Problem with RenderTarget2D fills up the graphic card memory

Hello out there,

I’m currently working at a Postprocessor class for my game. Now the effects are working fine but the graphic card memory is filling up. After around 1 Minute, 80% of the memory is used. It’s seems like the postprocessor isn’t deleting the RenderTarget2D without difficulty from the device.

the code I’m using is the following:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Brick
{
    class Postprocessor
    {
        private Effect CurrentEffect;
        private Effect DefaultEffect;
        private bool Multipass;

        private RenderTarget2D FinalImage;
        private RenderTarget2D EmptyTarget;

        public int Passescount
        {
            get 
            {
                return CurrentEffect.CurrentTechnique.Passes.Count;
            }
        }

        public Postprocessor(Effect EffectToUse, Effect StandardEffect)
        {
            if (!ChangeCurrentEffect(EffectToUse))
                return;
            DefaultEffect = StandardEffect;
            EmptyTarget = new RenderTarget2D(DefaultEffect.GraphicsDevice, GameSettings.WindowWidth, GameSettings.WindowHeight);
        }
        public bool ChangeCurrentEffect(Effect EffectToUse)
        {
            CurrentEffect = EffectToUse;
            if (CurrentEffect.CurrentTechnique.Passes.Count > 1)
                Multipass = true;
            else
                Multipass = false;
            return true;
        }

        private bool CheckIfParameterExist(string Name)
        {
            foreach (EffectParameter parameter in CurrentEffect.Parameters)
            {
                if (parameter.Name == Name)
                    return true;
            }
            return false;
        }

        public void Update(GameTime gametime, Vector2 MousePosition)
        {
            SetParameters((float)gametime.TotalGameTime.TotalSeconds, MousePosition);
        }

        private void SetParameters(float gametime, Vector2 MousePosition)
        {
            if (CheckIfParameterExist("GameTime"))
                CurrentEffect.Parameters["GameTime"].SetValue(gametime);
            if (CheckIfParameterExist("MousePosition"))
                CurrentEffect.Parameters["MousePosition"].SetValue(MousePosition);
        }

        public void Draw(GraphicsDevice graphics, RenderTarget2D Target, SpriteBatch SB)
        {
            if (Multipass)
            {
                FinalImage = MultiCallPostprocess(graphics, SB, Target);
            }
            else
            {
                FinalImage = SinglePostprocessCall(graphics, SB, Target, 0);
            }
            graphics.Clear(Color.Transparent);
            DefaultEffect.CurrentTechnique.Passes[0].Apply();
            SB.Draw(FinalImage, new Rectangle(0, 0, FinalImage.Width, FinalImage.Height), Color.White);
        }

        private RenderTarget2D MultiCallPostprocess(GraphicsDevice graphics, SpriteBatch SB, RenderTarget2D DrawTarget)
        {
            RenderTarget2D ReturnImage = DrawTarget;
            for (int i = 0; i < CurrentEffect.CurrentTechnique.Passes.Count; i++)
            {
                ReturnImage = SinglePostprocessCall(graphics, SB, ReturnImage, i);
            }
            return ReturnImage;
        }

        private RenderTarget2D SinglePostprocessCall(GraphicsDevice graphics, SpriteBatch SB, RenderTarget2D DrawTarget, int PassPosition)
        {
            RenderTarget2D ReturnImage = new RenderTarget2D(graphics, DrawTarget.Width, DrawTarget.Height);
            if (CurrentEffect.CurrentTechnique.Passes.Count >= PassPosition)
            {
                graphics.SetRenderTarget(ReturnImage);
                graphics.Clear(Color.Transparent);
                CurrentEffect.CurrentTechnique.Passes[PassPosition].Apply();
                SB.Draw(DrawTarget, new Rectangle(0, 0, DrawTarget.Width, DrawTarget.Height), Color.White);
                graphics.SetRenderTarget(null);
            }
            else
            {
                ReturnImage = DrawTarget;
            }
            return ReturnImage;
        }
        
    }
}

The 2 Shaders I’m using to test this looks like this:

DefaultShader:

sampler TextureSampler : register(s0);

float4 Default(float4 position : SV_Position, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
    float4 tex = tex2D(TextureSampler, texCoord);
    return tex*color;
}

technique DefaultTechnique
{
    pass Default
    {
        PixelShader = compile ps_3_0 Default();
    }
}

CurrentEffect:

sampler TextureSampler : register(s0);

float2 MousePosition = { 0.5, 0.5 };

float4 BlackWhite(float4 position : SV_Position, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
    float4 tex = tex2D(TextureSampler, texCoord);
    tex.gb = tex.r;
    return tex*color;
}

float4 RadialBlur(float2 texCoord : TEXCOORD0 ) : COLOR
{
    float BlurStart = 1.0f;
    float BlurWidth = -0.1;
    int nsamples = 10;    
    texCoord -= MousePosition;  float4 c = 0;
    for(int i=0; i <nsamples; i++)
    {
        float scale = BlurStart + BlurWidth*(i/(float) (nsamples-1));
        c += tex2D(TextureSampler, texCoord * scale + MousePosition);
    }
    c /= nsamples;
    return c;
}


technique Technique1
{

    pass BlackWhite
    {
        PixelShader = compile ps_3_0 BlackWhite();
    }

    pass RadialBlur
    {
        PixelShader = compile ps_3_0 RadialBlur();
    }
}

The Draw call in my Game1 class is this:

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
        float Time = (float)gameTime.TotalGameTime.TotalSeconds / 100;
        // TODO: Add your drawing code here
        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
        GraphicsDevice.SetRenderTarget(PostProcessingTarget);
        WindowManager.Draw(spriteBatch);
        if (GameSettings.MouseVisible)
            spriteBatch.Draw(MouseCursor, cursorPos, Color.White);
        GraphicsDevice.SetRenderTarget(null);
           PostProcessingTarget.Height), Color.White);
        PostEffectProcessor.Draw(GraphicsDevice, PostProcessingTarget, spriteBatch);
        if (ShowDebugInfo)
            spriteBatch.DrawString(basic, String.Format("FPS: {1}{0}CPU Load: {2}{0}Active Windows: {3}{0}Postprocessor passes: {4}", Environment.NewLine, Debug.FrameCounter.GetAverageFrames(), Debug.CPULoad.GetCurrentCPULoad(), WindowManager.ActiveScreenNumber(), PostEffectProcessor.Passescount), new Vector2(10, 10), Color.Red);
        spriteBatch.End();
        base.Draw(gameTime);
    }

If I change the following in my Postprocessor class the memory leak is gone. But now if i use 2 effects the outcome is strange:

        private RenderTarget2D SinglePostprocessCall(GraphicsDevice graphics, SpriteBatch SB, RenderTarget2D DrawTarget, int PassPosition)
        {
            RenderTarget2D ReturnImage = EmptyTarget;
            if (CurrentEffect.CurrentTechnique.Passes.Count >= PassPosition)
            {
                graphics.SetRenderTarget(ReturnImage);
                graphics.Clear(Color.Transparent);
                CurrentEffect.CurrentTechnique.Passes[PassPosition].Apply();
                SB.Draw(DrawTarget, new Rectangle(0, 0, DrawTarget.Width, DrawTarget.Height), Color.White);
                graphics.SetRenderTarget(null);
            }
            else
            {
                ReturnImage = DrawTarget;
            }
            return ReturnImage;
        }

Creating a new RenderTarget2D every frame gives the following output on the screen:
(Memory leak)

After some seconds there is a lot of data in the graphic card memory.

if i change the line to use the EmptyTarget the output is this:
(No Memory leak)

the memory usage on the graphic card is around 11% (it doesn’t increase overtime)

Update

If the shader contains only 1 pass the code using the EmptyTarget just work fine.

Any ideas what I’m doing wrong i don’t get it …

Thanks for your help.

Creating a screen size RenderTarget2D for each pass every frame is going to be horribly slow and memory consuming to start with. But the main killer here is that you never call Dispose() on any of those RenderTarget2D instances when you are finished with them. That is required in order to release the unmanaged resources (in this case, GPU memory) held by those objects.

Ideally, you would allocate required RenderTarget2D instances at initialization and re-use them during the game. Then dispose of them all at the end during shutdown.

Thanks @KonajuGames for the fast help.

I’ve already tried to create an empty RenderTarget2D in the initialization. It’s the second attempt where the shader doesn’t work at all if there are more than one pass. How to solve this problem?

   public Postprocessor(Effect EffectToUse, Effect StandardEffect)
    {
        if (!ChangeCurrentEffect(EffectToUse))
            return;
        DefaultEffect = StandardEffect;
        EmptyTarget = new RenderTarget2D(DefaultEffect.GraphicsDevice, GameSettings.WindowWidth, GameSettings.WindowHeight);
    }



private RenderTarget2D SinglePostprocessCall(GraphicsDevice graphics, SpriteBatch SB, RenderTarget2D DrawTarget, int PassPosition)
{
    RenderTarget2D ReturnImage = EmptyTarget;
    if (CurrentEffect.CurrentTechnique.Passes.Count >= PassPosition)
    {
        graphics.SetRenderTarget(ReturnImage);
        graphics.Clear(Color.Transparent);
        CurrentEffect.CurrentTechnique.Passes[PassPosition].Apply();
        SB.Draw(DrawTarget, new Rectangle(0, 0, DrawTarget.Width, DrawTarget.Height), Color.White);
        graphics.SetRenderTarget(null);
    }
    else
    {
        ReturnImage = DrawTarget;
    }
    return ReturnImage;
}

I don’t know I where should dispose the RenderTarget2D. I think the best place is in the main draw in the Game1 after spriteBatch.End() ?

Update

Seems like this line RenderTarget2D ReturnImage = EmptyTarget creates not a copy it’s more like a reference. I don’t understand it :smiley: how to create a postprocessor without memory leak or the other weird problem?

Disposing the RenderTarget2D at runtime doesn’t solve the memory leak.

Update2

Solved the leak and drawing problem with the following function:

    private RenderTarget2D CloneRenderTarget(RenderTarget2D target)
    {
        var clone = new RenderTarget2D(target.GraphicsDevice, target.Width,
            target.Height, target.LevelCount > 1, target.Format,
            target.DepthStencilFormat, target.MultiSampleCount,
            target.RenderTargetUsage);
        for (int i = 0; i < target.LevelCount; i++)
        {
            double rawMipWidth = target.Width / Math.Pow(2, i);
            double rawMipHeight = target.Height / Math.Pow(2, i);
            // make sure that mipmap dimensions are always > 0.
            int mipWidth = (rawMipWidth < 1) ? 1 : (int)rawMipWidth;
            int mipHeight = (rawMipHeight < 1) ? 1 : (int)rawMipHeight;
            var mipData = new Color[mipWidth * mipHeight];
            target.GetData(i, null, mipData, 0, mipData.Length);
            clone.SetData(i, null, mipData, 0, mipData.Length);
        }
        return clone;
    }

I found it here:

Now my Postprocessor looks like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Brick
{
    class Postprocessor
    {
        private Effect CurrentEffect;
        private Effect DefaultEffect;
        private bool Multipass;
        private RenderTarget2D FinalImage;
        private RenderTarget2D EmptyTarget;
        public int Passescount
        {
            get 
            {
                return CurrentEffect.CurrentTechnique.Passes.Count;
            }
        }
        public Postprocessor(Effect EffectToUse, Effect StandardEffect)
        {
            if (!ChangeCurrentEffect(EffectToUse))
                return;
            DefaultEffect = StandardEffect;
            EmptyTarget = new RenderTarget2D(DefaultEffect.GraphicsDevice, GameSettings.WindowWidth, GameSettings.WindowHeight);
        }
        public bool ChangeCurrentEffect(Effect EffectToUse)
        {
            CurrentEffect = EffectToUse;
            if (CurrentEffect.CurrentTechnique.Passes.Count > 1)
                Multipass = true;
            else
                Multipass = false;
            return true;
        }
        private bool CheckIfParameterExist(string Name)
        {
            foreach (EffectParameter parameter in CurrentEffect.Parameters)
            {
                if (parameter.Name == Name)
                    return true;
            }
            return false;
        }
        public void Update(GameTime gametime, Vector2 MousePosition)
        {
            SetParameters((float)gametime.TotalGameTime.TotalSeconds, MousePosition);
        }
        private void SetParameters(float gametime, Vector2 MousePosition)
        {
            if (CheckIfParameterExist("GameTime"))
                CurrentEffect.Parameters["GameTime"].SetValue(gametime);
            if (CheckIfParameterExist("MousePosition"))
                CurrentEffect.Parameters["MousePosition"].SetValue(MousePosition);
        }
        public void Draw(GraphicsDevice graphics, RenderTarget2D Target, SpriteBatch SB)
        {
            if (Multipass)
            {
                FinalImage = MultiCallPostprocess(graphics, SB, Target);
            }
            else
            {
                FinalImage = SinglePostprocessCall(graphics, SB, Target, 0);
            }
            graphics.Clear(Color.Transparent);
            DefaultEffect.CurrentTechnique.Passes[0].Apply();
            SB.Draw(FinalImage, new Rectangle(0, 0, FinalImage.Width, FinalImage.Height), Color.White);
        }
        private RenderTarget2D MultiCallPostprocess(GraphicsDevice graphics, SpriteBatch SB, RenderTarget2D DrawTarget)
        {
            for (int i = 0; i < CurrentEffect.CurrentTechnique.Passes.Count; i++)
            {
                DrawTarget = SinglePostprocessCall(graphics, SB, DrawTarget, i);
            }
            return DrawTarget;
        }
        private RenderTarget2D SinglePostprocessCall(GraphicsDevice graphics, SpriteBatch SB, RenderTarget2D DrawTarget, int PassPosition)
        {
            EmptyTarget.Dispose();
            EmptyTarget = new RenderTarget2D(graphics, DrawTarget.Width, DrawTarget.Height);
            if (CurrentEffect.CurrentTechnique.Passes.Count >= PassPosition)
            {
                graphics.SetRenderTarget(EmptyTarget);
                graphics.Clear(Color.Transparent);
                CurrentEffect.CurrentTechnique.Passes[PassPosition].Apply();
                SB.Draw(DrawTarget, new Rectangle(0, 0, DrawTarget.Width, DrawTarget.Height), Color.White);
                graphics.SetRenderTarget(null);
            }
            else
            {
                EmptyTarget = DrawTarget;
            }
            return CloneRenderTarget(EmptyTarget);
        }
        private RenderTarget2D CloneRenderTarget(RenderTarget2D target)
        {
            var clone = new RenderTarget2D(target.GraphicsDevice, target.Width,
                target.Height, target.LevelCount > 1, target.Format,
                target.DepthStencilFormat, target.MultiSampleCount,
                target.RenderTargetUsage);
            for (int i = 0; i < target.LevelCount; i++)
            {
                double rawMipWidth = target.Width / Math.Pow(2, i);
                double rawMipHeight = target.Height / Math.Pow(2, i);
                // make sure that mipmap dimensions are always > 0.
                int mipWidth = (rawMipWidth < 1) ? 1 : (int)rawMipWidth;
                int mipHeight = (rawMipHeight < 1) ? 1 : (int)rawMipHeight;
                var mipData = new Color[mipWidth * mipHeight];
                target.GetData(i, null, mipData, 0, mipData.Length);
                clone.SetData(i, null, mipData, 0, mipData.Length);
            }
            return clone;
        }
    }
}