[SOLVED] Help Dispose of RenderTarget2D

starting with the…

TL;DR: I believe that the code I’m using to create the RenderTarget2D’s for each scene is causing a buildup of memory from the Graphics Device. I’m looking for any advice for properly creating RenderTagerts which will later be disposed of. Thanks.

I’ve been using a template I’ve made for games and realized that every time I create and delete a scene layer a significant amount of Process Memory is leftover. While I still believe that it is my improper disposal code, I think that the fact I am making a new RenderTarget2D every scene using the same GraphicsDevice is causing the issue.

When I look at the Diagnostic Tool after creating then deleting/disposing over 50 instances of scenes, there is a single instance of a List which is not being disposed of. It contains Microsoft.Xna.Framework.Graphics.GraphicsDevice, which leads me to believe the GraphicsDevice must be handled. It also shows that ContentManagers and RenderTargets are properly removed though.

If I wish to create multiple scenes layered on top of each other, each with their own RenderTarget, should I simply have each scene inherit for the (Game) class. Would this solve my disposal problem? This seems inefficient.

I am unsure if I am just trying to do something MonoGame wasn’t made for. Any advice of direction is very much appreciated. I’ll post a simplified version of my template below.

public class Game1 : Game
{
    private GraphicsDeviceManager _graphics;
    private SpriteBatch _spriteBatch;

    // Hold the dimensions of the screen as a Point
    private Vector2 dimensions = new Vector2(1055, 635);
    // Hold the scene layers in a list
    private List<Scene> scenes = new List<Scene>();

    public Game1()
    {
        _graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
        IsMouseVisible = true;
    }

    protected override void Initialize()
    {
        //Initialize
        // Create the window constraits
        _graphics.PreferredBackBufferWidth = (int)dimensions.X;
        _graphics.PreferredBackBufferHeight = (int)dimensions.Y;
        _graphics.ApplyChanges();
        Window.Title = "Random Colors";

        base.Initialize();
    }

    protected override void LoadContent()
    {
        _spriteBatch = new SpriteBatch(GraphicsDevice);
    }

    protected override void Update(GameTime gameTime)
    {
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
            Exit();

        if (Keyboard.GetState().IsKeyDown(Keys.Space))
        {
            // Add a new scene
            scenes.Add(new ColorBox(Services, GraphicsDevice, new Vector2(200, 200), ColorTool.RandomColor()));
        }
        // Delete and dispose of the scenes
        else if (Keyboard.GetState().IsKeyDown(Keys.Back) && scenes.Count != 0)
        {
            Scene toDelS = scenes[scenes.Count - 1];
            scenes.RemoveAt(scenes.Count - 1);
            toDelS.Dispose();
            toDelS = null;
        }
        else if (Keyboard.GetState().IsKeyDown(Keys.C)) { System.GC.Collect(); }

        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        // Render each scene
        foreach(Scene s in scenes) { s.RenderToTarget(GraphicsDevice, _spriteBatch, gameTime); }

        GraphicsDevice.Clear(Color.Wheat);

        // Prepare the spritebatch
        _spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend,
            SamplerState.LinearClamp, DepthStencilState.Default,
            RasterizerState.CullNone);

        // Draw each scene to the menu
        for (int i = 0; i < scenes.Count; i++)
        {
            _spriteBatch.Draw(scenes[i].RenderTarget, new Rectangle((105 * (i % 10) + 5), (i / 10) * 105 + 5, 100, 100), Color.White);
        }

        // End the draw function
        _spriteBatch.End();

        base.Draw(gameTime);
    }
    }

The Abstract Scene

abstract class Scene : IDisposable
{
    /// <summary>
    /// Implement Dispose
    /// </summary>
    // Calls Dispose(true)
    protected bool isDisposed;
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    protected virtual void Dispose(bool disposing)
    {
        if (isDisposed) return;
        if (disposing)
        {
            if (Content != null)
            {
                Content.Unload();
                Content.Dispose();
                Content = null;
            }
            if (RenderTarget != null)
            {
                RenderTarget.Dispose();
                RenderTarget = null;
            }
        }
        isDisposed = true;
    }

    // What the scene is drawn to
    public RenderTarget2D RenderTarget { get; private set; }
    public ContentManager Content { get; private set; }
    // Scene dimensions       
    public Vector2 SceneDimension { get; set; }

    /// <summary>
    /// Create a generic scene which hold objects, child scenes, and preforms actions, and draws to the screne
    /// </summary>
    /// <param name="service">The service provider for the scene</param>
    /// <param name="GraphicsDevice">The source used to create a render target</param>
    /// <param name="sceneDimension">The vector2 declaring the dimenstion of the screne</param>
    public Scene(IServiceProvider service, GraphicsDevice GraphicsDevice, Vector2 sceneDimension)
    {
        SceneDimension = sceneDimension;

        // Create a new content manager for just this scene
        Content = new ContentManager(service, "Content");

        // Create the graphics manager just for this scene
        RenderTarget = new RenderTarget2D(GraphicsDevice, (int)sceneDimension.X, (int)sceneDimension.Y,
            false, GraphicsDevice.PresentationParameters.BackBufferFormat, DepthFormat.Depth24);
    }

    public abstract Boolean Update(GameTime gameTime);
    public abstract void RenderToTarget(GraphicsDevice GraphicsDevice, SpriteBatch spriteBatch, GameTime gameTime);
}

The Child

class ColorBox : Scene
{
    Texture2D blankTex;

    Color col1, col2;
    Rectangle drawRec = new Rectangle(25,25,150,150);

    /// <summary>
    /// Dispose code
    /// </summary>
    protected override void Dispose(bool disposing)
    {
        if (isDisposed) return;

        if (disposing)
        {
            // Dispose managed resources
            blankTex.Dispose();
        }

        isDisposed = true;

        // Call base class implementation.
        base.Dispose(disposing);
    }

    public ColorBox(IServiceProvider service, GraphicsDevice graphicsDevice, Vector2 screenDim, Color c1) : base(service, graphicsDevice, screenDim)
    {
        // Load the texture
        blankTex = Content.Load<Texture2D>("blank");

        // Create the first and second color
        col1 = c1;
        col2 = ColorTool.CompositeColor(col1);
    }

    public override void RenderToTarget(GraphicsDevice GraphicsDevice, SpriteBatch spriteBatch, GameTime gameTime)
    {
        // Set the render target
        GraphicsDevice.SetRenderTarget(RenderTarget);
        GraphicsDevice.DepthStencilState = new DepthStencilState() { DepthBufferEnable = true };

        // Refresh
        GraphicsDevice.Clear(col1);
        // Begin spritebatch
        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied, SamplerState.LinearWrap, null, null, default);

        // Draw the square
        spriteBatch.Draw(blankTex, drawRec, col2);

        // Drop the spritebatch
        spriteBatch.End();
        // Drop the render target
        GraphicsDevice.SetRenderTarget(null);
        return;
    }

    public override bool Update(GameTime gameTime) { return true; }
}

I don’t really know much about this, but something occurs to me that you can check. Have you tried forcing a GC.Collect call? There’s a couple of parameters there to super force it, but I’m wondering if the garbage collector just hasn’t bothered to get around to cleaning that up.

Anyway, a couple of things…

  1. You may not actually need to create a new RenderTarget2D for each scene. I believe you can render to an existing one. You’d need to clear it to transparent first and then render in the appropriate order, but it’s an option.
  2. How many scenes do you expect to have? How much memory is being built up? Is this too much or acceptable?

Also, I notice that you’re creating a new ContentManager object for each Scene as well. I believe you can just pass along the one that your Game creates and use that for loading content, can’t you? I did this in my own game and didn’t have any issues, but it was also small enough that I could load all my textures at the start in a linear, single-threaded fashion.

1 Like

I’ve called Garbage Collection and while this fixes the excess of WeakRefrences the accumulation of memory is still an issue.

  1. I want to render to target so I can draw multiple instances of a single scene If I wish to, and easily treat a scene as a transparent texture above another one.

  2. I want to program the project dynamically so that I can display as many or little scenes as required. My main concern is if I make a scene for each time the player enters into a new level then remove it when they leave, I this will result in multiple increases to the memory just from playing the game over time.

I want this code to be usable on a large scale. For smaller projects I only have 1 render target and 1 content manager (using the tips you describe).

This code is just an incredibly simplified example version of the project I’m working on; so that I can narrow down the memory issue. My actual projects have very different scenes and Game1 setup.

Calling rendertarget.dispose will clear up the memory. You need to make sure you dispose any that are you.

If your planning on using lots of render targets how about have a static Display Controller Class. (You could also store some other display stuff in here, framerate, resolutions, the graphicsdevice)

This class holds a dictionary of int and render targets

static class DisplayController
{
	private static Dictionary<int, RenderTarget2D> renderTargetList;
	private static int currentTargetId = 0;
	public static GraphicsDevice graphicsDevice;
	
	 public static int AddRenderTarget(int targetWidth, int targetHeight)
    {
		currentTargetId++;
        renderTargetList.Add(currentTargetId, new RenderTarget2D(DisplayController.graphicsDevice, targetWidth, targetHeight, false, SurfaceFormat.Bgr565, DepthFormat.None, 0, RenderTargetUsage.PreserveContents));
        return currentTargetId;
    }
	
    public static RenderTarget2D GetRenderTarget(int id)
    {
        return renderTargetList[id];
    }	
	
	public static void DisposeRenderTarget(int id)
    {
        if (renderTargetList.ContainsKey(id))
        {
            renderTargetList[id].Dispose();
            renderTargetList.Remove(id);
        }
    }
}

The int is the index to a rendertarget. Have a static method to add a rendertarget which returns the index of the rendertarget

Have a method that retrieves the render target by passing in the int index value. Then also have a method that can dispose of an indexed render target and clear it from your dictionary.

Then in your object store any references as in index to a rendertarget2d as you require them. To dispose of them pass in that index to disposerendertarget()

int renderTarget1 = DisplayController.AddRenderTarget(1920,1080);
DisplayController.graphicsDevice.SetRenderTarget(DisplayController.GetRenderTarget(int 
renderTarget1));
DisplayController.DisposeRenderTarget(renderTarget1);

Actually, if forcing garbage collection cleans things up, I wouldn’t be worried. I’m not actually suggesting that you do this, it was just a test to see if it reclaimed the memory. The garbage collector just kinda does what it thinks it needs to do, and so if it’s not cleaning it that memory it’s because it doesn’t think it has to. When that changes, it will clean it up.

If you forced a collection and the references were still hanging around, that would suggest a leak. Since that’s not the case, you can probably safely ignore this.

Does this jive with your findings?

Sorry I was unclear, I meant to say I was already calling GC on the code and there is still a memory leak. GC only cleans up some of the leftovers form the deleted scene. The instance in the image provided (above in my post) is the only significant increase in memory after calling both dispose and GC.

You should only make the rendertargets before the update and draw loop if possible and reuse them in update and draw. But not reassign new instances to those references.
If your creating them in the running loop and you reassign to the reference then the gc can’t clean up on its side because how can it. On the other end you have zero control on the gpu’s side for when or how it handles its own memory.

You should dispose of them when they will no longer be used and reuse them by simply clearing them and then writing to them again.
Most of the time you don’t even really have to dispose of them unless you are going to need the memory back and aren’t using them like your next level doesn’t or something.
The gc has very little say in texture and gpu memory it can only deal with the vm / ram side of storage of related resources and id be surprised if that memory wasn’t pinned.

3 Likes

Oh I see. Sorry then, I have no further ideas at this time.

Also, consider using using statements.

It may or may not suit your program’s flow, depending on when you need to allocate and deallocate

using(var target=CreateYourRenderTarget())
{
   //All statements that need target
}
//now it's disposed properly, and transparently

Some other things to think about:

  • GPU memory isn’t managed. That’s not just garbage collection. If you allocate and de-allocate a lot, you’ll end up fragmenting the memory, and large blocks of VRAM may become impossible to acquire, even if you have the space. Especially if you allocate every single frame or something crazy like that.
  • Creating a render target is a slow operation compared to clearing one. If the target you’re making over and over has largely the same attributes, except for size, consider writing to only part of it or scaling your results. Again that does depend on your needs.
2 Likes

So if I’m understanding correctly I’m going about this wrong. Instead of creating a new render target every time a new level is loaded, then disposing of it when the level is over; I should have a single render target for levels, that a level classes accesses upon loading and is reused by the next level class after it?

Thank you. Fragmenting memory was something I wasn’t even considering.

I wanted to write my code so that each scene could be loaded once with its own size for RenderTarget. But I think I’m going to use a static dictionary of RenderTargets based on thier size, as I am only planning on 3 or 4 different sizes per game.

Yeah, that’s my understanding, at least. I don’t know the underlying implementation of gpu memory management in monogame, and maybe it’s smarter than I think.

If you’re only doing it per level, that’s probably not hurting your memory much. I was afraid of per frame.

I’ll go with willmotil on this and just render game entities bound to that rendertarget.

But in any rate this how I dispose my disposable class which I think might contains unmanaged code, try to put a destructor and call dispose to false and destroy monogame objects outside of if( disposing ) and tag IsDispose if only on disposing whether explicit dispose by true or called by destructor monogame objects will be dispose.

abstract class Scene : IDisposable
{

    // MEMBERS
    protected bool _IsDisposed;    
    public RenderTarget2D RenderTarget{ get; private set; }

    // CONTRUCTOR
    public Scene(IServiceProvider service, GraphicsDevice GraphicsDevice, Vector2 sceneDimension)
    {

        // Create the graphics manager just for this scene
        RenderTarget = new RenderTarget2D (GraphicsDevice, (int)sceneDimension.X, (int)sceneDimension.Y,
            false, GraphicsDevice.PresentationParameters.BackBufferFormat, DepthFormat.Depth24);
    }

    // DESTRUCTOR
    ~Scene()
    {
        Dispose(false);
    }

    // METHODS
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
         // EDIT if (_IsDisposed) return;
         if (  disposing && _IsDisposed ) return;


        if ( disposing )
        {

            // Dispose your managed objects here

            _IsDisposed = true;   

        }

        // Dispose your Monogame objects here which 
        // most likely contains unmanaged code : )
       
        if ( RenderTarget != null )
        {
             RenderTarget.Dispose();
             RenderTarget = null;
        }            

       
    }

}
1 Like

Thank you all for the support. This was helpful. I’m going to mark this as solved, with some notes:

  • My solution for the time will be to limit the number of RenderTargets to the number of diff scene dimensions. I will post the code I write for this soon (to make this post feel more solved).

I appreciate all your help. Cheers :heart:

1 Like