RenderTarget2D.Dispose errors.

Hi,

So here I have a DesktopGL project with MonoGame 3.6.0

In my project I use RenderTarget2D one can everywhere for various use.

I get some NullReferenceException here is the stacktrace:

NullReferenceException: 
  Module "Microsoft.Xna.Framework.Graphics.RenderTarget2D", line 0, col 0, in Dispose { <lambda> }
	Void <Dispose>b__c()
  Module "Microsoft.Xna.Framework.Threading+<>c__DisplayClass1", line 0, col 0, in BlockOnUIThread { <lambda> }
	Void <BlockOnUIThread>b__0()
  Module "Microsoft.Xna.Framework.Threading", line 42, col 0, in Run
	Void Run()
  Module "Microsoft.Xna.Framework.SdlGamePlatform", line 33, col 0, in RunLoop
	Void RunLoop()
  Module "Microsoft.Xna.Framework.Game", line 139, col 0, in Run
	Void Run(Microsoft.Xna.Framework.GameRunBehavior)
  File "Program.cs", line 439, col 21, in ‮‫‌‍‎‌‍‪‏‎‎‎‎‮‌‪‎‪​‍‍​‭‮
	Int32 ‮‫‌‍‎‌‍

I think it’s due to the GC trying to Dispose a RenderTarget2D, or something like that.

So I created a class to manage my RenderTarget2Ds.

Here is the class:

// ==========================================================================================================================================
//  ♦ Buffer2D.cs
// ------------------------------------------------------------------------------------------------------------------------------------------
//
// ==========================================================================================================================================

using Microsoft.Xna.Framework.Graphics;
using System;
using System.Drawing;
using Color = Microsoft.Xna.Framework.Color;
using Rectangle = Microsoft.Xna.Framework.Rectangle;

namespace Engine127 {
	// ==========================================================================================================================================
	//  o class Buffer2D
	// ==========================================================================================================================================
	public class Buffer2D : IDisposable {
		private RenderTarget2D _buffer;
		private readonly RenderTargetUsage _usage;
		//
		// ------------------------------------------------------------------------------------------------------------------------------------------
		//  • Constructors
		// ------------------------------------------------------------------------------------------------------------------------------------------
		//
		public Buffer2D(int width, int height, RenderTargetUsage usage = RenderTargetUsage.DiscardContents) {
			this._usage = usage;
			this._buffer = GetNewRenderTarget(width, height, usage);
		}
		//
		// ------------------------------------------------------------------------------------------------------------------------------------------
		//  • Destructor
		// ------------------------------------------------------------------------------------------------------------------------------------------
		//
		~Buffer2D() => Dispose(false);
		//
		// ------------------------------------------------------------------------------------------------------------------------------------------
		//  • GetRenderTarget : RenderTarget2D
		// ------------------------------------------------------------------------------------------------------------------------------------------
		// 
		private static RenderTarget2D GetNewRenderTarget(int width, int height, RenderTargetUsage usage) => new RenderTarget2D(GraphicsDevice, width, height, false, SurfaceFormat.Color, GraphicsDevice.PresentationParameters.DepthStencilFormat, GraphicsDevice.PresentationParameters.MultiSampleCount, usage);
		//
		// ------------------------------------------------------------------------------------------------------------------------------------------
		//  • Reset : void
		// ------------------------------------------------------------------------------------------------------------------------------------------
		// 
		public void Reset(int width, int height) {
			DisposeTextureAsync(this._buffer); // Add the buffer in a List<Texture2D> so the Main thread can dispose it
			this._buffer = GetNewRenderTarget(width, height, this._usage);
		}
		//
		// ------------------------------------------------------------------------------------------------------------------------------------------
		//  • Draw : void
		// ------------------------------------------------------------------------------------------------------------------------------------------
		// 
		public void Draw(Rectangle past, Rectangle? copy = null, Color? color = null) {
			CheckDisposed();
			DrawOnScreen(this._buffer, past, copy ?? this._buffer.Bounds, color ?? Color.White);
		}
		//
		// ------------------------------------------------------------------------------------------------------------------------------------------
		//  • SetAsRenderTarget : void
		// ------------------------------------------------------------------------------------------------------------------------------------------
		// 
		public void SetAsRenderTarget(Color? color = null) {
			CheckDisposed();
			GraphicsDevice.SetRenderTarget(this._buffer);
			if (this._usage == RenderTargetUsage.DiscardContents) GraphicsDevice.Clear(color ?? Color.Transparent);
		}
		//
		// ------------------------------------------------------------------------------------------------------------------------------------------
		//  • CheckDisposed : void
		// ------------------------------------------------------------------------------------------------------------------------------------------
		//
		private void CheckDisposed() {
			if (this._buffer?.IsDisposed ?? true) throw new AccessViolationException("Buffer2D disposed");
		}
		//
		// ------------------------------------------------------------------------------------------------------------------------------------------
		//  • Properties
		// ------------------------------------------------------------------------------------------------------------------------------------------
		//
		public int Width => this._buffer.Width;
		public int Height => this._buffer.Height;
		public Rectangle Bounds => this._buffer.Bounds;
		//
		// ------------------------------------------------------------------------------------------------------------------------------------------
		//  • Dispose : void
		// ------------------------------------------------------------------------------------------------------------------------------------------
		//
		public void Dispose() {
			Dispose(true);
			GC.SuppressFinalize(this);
		}
		//
		// ------------------------------------------------------------------------------------------------------------------------------------------
		//  • Dispose *internal*
		// ------------------------------------------------------------------------------------------------------------------------------------------
		//
		private void Dispose(bool notCalledFromGc) {
			if (!this._buffer?.IsDisposed ?? false) DisposeTextureAsync(this._buffer); // Add the buffer in a List<Texture2D> so the Main thread can dispose it
		}
	}
	// --- END namespace ---
}

The main thread diposing:

        lock (TexturesToDisposeLock) {
            int dispNum = this.TexturesToDispose.Count;
            while (dispNum > 0) {
                Texture2D texture = this.TexturesToDispose.First();
                this.TexturesToDispose.RemoveAt(0);
                //
                if (texture != null) {
                    try {
                        if (!texture.IsDisposed) texture.Dispose();
                    } catch (Exception exception) {
                        Log("Disposing error");
                        Log(exception.ToString());
                    }
                }
                //
                dispNum--;
            }
        }

The problem is that I still get NullReferenceException type error by RenderTarget2D.Dispose …
An idea of where that might come from?

Are you trying to thread rendertargets onto seperate threads ?

It’s hard to say but i think your doing something fundamentally wrong somewere.
In the old days disposing/releasing null memory like that (freeing a dangling reference pointer) was possibly the worst bug you could create. It would have catastrophic results for random files or potentially your operating system itself.

Anyways SuppressFinalize is not gaurenteed im not even sure what that would do in that context. There is no gaurentee when a reference instance will have its memory freed after you call dispose on it. Nor can there be from your end, that is not under your control. Especially in this context where the gpu driver is involved with the reference.

You can’t call dispose on a null reference. ( os c# will call it to stop a segementation fault )
You should not dispose the same reference twice. ( that would be freeing dangling memory os c# should call it)
Before you reassign a new instance to the same reference, you should dispose it. (memory leak)
It should never be null unless you disposed it already and set it null or it has never been assigned to.

Thats just off the top of my head but ya its very tricky.
Why are you reassigning these rendertargets with new instances so much and not just clearing them and reusing them instead ? Or is this occuring when your switching modes or ending the game ?

In fact all instances of Buffer2D are created and destroyed by the main thread

I know I can’t dispose a null pointer or dispose something twice I check that before calling Dispose.

I’m just trying to make sure that if a Buffer2D is Dispose by the garbage collector, the RenderTarget2D reference will be disposed correctly by the main thread.

Buffer2D is mainly used for windows like this one:


Each object is in 3D and rendered in a different Buffer2D.

All seems to work fine for me but players keep getting NullReferenceException and I don’t know why.

That message “NullReferenceException” has only one meaning.

You are assigning to or reading from a null instance.
In this context though its possible something is calling dispose twice or dispose on a null instance.

I check that before calling Dispose

Be aware it is possible to dispose something and IsDisposed return false if that is not been done yet its really greasy stuff which you don’t have a lot of assurances over.
I really don’t think its good for you to be calling suppress finalize in this context either as you don’t own the rendertarget’s dispose it may or may not matter.

I dunno good luck though id copy the project and start tearing out the safetys and try to force the bug to happen to identify the cause.

Edit: mind you im no expert on monogames dispose chain.

But it looks like what you have done is a bit of overkill.
I think all you really need to be doing is just have a dispose call and call dispose on the rendertarget in your class. It looks like it the rendertargets rescource has a finalizer anyways.

// GraphicsResource.cs

//If disposing is false, the GraphicsDevice may or may not be disposed yet.

    ~GraphicsResource()
    {    
     // Pass false so the managed objects are not released      
         Dispose(false);
    }

       public void Dispose()
       {
            // Dispose of managed objects as well
            Dispose(true);
            // Since we have been manually disposed, do not call the finalizer on this object
            GC.SuppressFinalize(this);
       }

Im probably wrong but
My hunch is that you are actually not explicitly calling dispose on something that goes out of scope.
Or that suppress finalize call you made is screwing up the rescources finalize as you have short circuted it early. As for how that could lead to a null reference i dunno so maybe im wrong but… It doesn’t look quite right from point a to b.

At the moment I’m trying to make sure that all Buffer2D instances are explicitly Disposed so that I do not have to compte on the GC and its black magic. It should make things a lot less complex…

Thank you for your help!