Why does this thrash my GPU?

The size and surface formats of the render targets don’t matter. Why does switching between render targets increase GPU usage when no changes have been made?

For the record:
Platform: Windows 10 cross-platform desktop project
MonoGame version: 3.7.0.1708
Graphics card: ASUS GeForce GTX 1060 6GB Dual OC

But if you create a new MonoGame project and paste this into Game1.cs I reckon you get the same results (high GPU usage)

Is there any other way of applying a render target than swapping it with something else?

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Test
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        RenderTarget2D renderTarget1, renderTarget2;
    
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredBackBufferWidth = 1920;
            graphics.PreferredBackBufferHeight = 1080;
            graphics.ApplyChanges();
            Content.RootDirectory = "Content";
        }

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            renderTarget1 = new RenderTarget2D(GraphicsDevice, 1, 1, false, SurfaceFormat.Color,
                DepthFormat.None, 0, RenderTargetUsage.PreserveContents);
            renderTarget2 = new RenderTarget2D(GraphicsDevice, 1, 1, false, SurfaceFormat.Color,
                DepthFormat.None, 0, RenderTargetUsage.PreserveContents);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            for (int i = 0; i < 1000; i++)
            {
                GraphicsDevice.SetRenderTarget(renderTarget1);
                GraphicsDevice.SetRenderTarget(renderTarget2);
            }

            GraphicsDevice.SetRenderTarget(null);

            base.Draw(gameTime);
        }
    }
}

Because you are basically applying 2000 StateChanges to the GPU per frame - and changing render targets is one of the most expensive state changes on the GPU (involves clearing chaches, flushing, postponing data, etc)

It doesn’t matter if changes are made, because it’s still a StateChange on the GPU

This is not how you use render targets i can’t see any possible reasoning for doing this.

    for (int i = 0; i < 1000; i++)
    {
        GraphicsDevice.SetRenderTarget(renderTarget1);
        GraphicsDevice.SetRenderTarget(renderTarget2);
    }

A render target is basically a extra back buffer on the gpu it is meant to be a temporary extra rendering buffer on the gpu this is used for special cases depth sorting or post processing pixel shader effects.

Is there any other way of applying a render target than swapping it with something else?

I made this is as a minimal how to example for using a render target to answer the above long ago.

Comment out your game1 class but not the name space part in your project
Copy paste just the game1 portion of the below game1 into your project and run it.
Comments are left detailing the usage all along the way and what is happening.

You don’t need a texture or a spritefont for the below example.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace HowToRenderTarget2D
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        public static Texture2D dotTexture;

        //
        // Ok so here is our rendertarget reference declaration we declare it at class scope.
        //
        RenderTarget2D renderTargetIsAOffScreenBuffer;


        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            base.Initialize();
        }

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

            //
            // We have to set up the render target, So we will instantiate it now here were we are sure the device is set up.
            // The method asks for a couple basic variables at least we will fill out the whole thing though.
            //
            renderTargetIsAOffScreenBuffer = new RenderTarget2D(GraphicsDevice, GraphicsDevice.PresentationParameters.BackBufferWidth, GraphicsDevice.PresentationParameters.BackBufferHeight, false, SurfaceFormat.Color, DepthFormat.None);

            // I should also say that we should also change its size if we are resizing the screen at least ussually people want to do that. 
            // We would do that in a method we add to window.ClientSizeChanged += OnResize;  
            // public void OnResize(){} is a void method you make up and add to game1, i dont want to get off topic though.

            dotTexture = TextureDotCreate(GraphicsDevice);
        }

        //
        // Well just make a dot texture for this example from a color array, which we can draw with.
        // There is also a get data method that will allow for pulling data from a texture to a array.
        //
        // These methods are not typically used in game regularly but for one time specific needs.
        // Typically these are used for creating a texture from scratch or for specific image editing operations.
        // Typically were you need or desire the exact opposite of a pixel shader operation which is faster.
        // The render target way is faster simple reliable and recommended.
        //
        public static Texture2D TextureDotCreate(GraphicsDevice device)
        {
            Color[] data = new Color[1];
            data[0] = new Color(255, 255, 255, 255);
            return TextureFromColorArray(device, data, 1, 1);
        }
        public static Texture2D TextureFromColorArray(GraphicsDevice device, Color[] data, int width, int height)
        {
            Texture2D tex = new Texture2D(device, width, height);
            tex.SetData<Color>(data);
            return tex;
        }

        protected override void UnloadContent()
        {
        }

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



            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);



            spriteBatch.Begin();

            // Set the drawing target to be a off screen rendering buffer.
            GraphicsDevice.SetRenderTarget(renderTargetIsAOffScreenBuffer);

            // Draw our dot texture on it but streach it out to a big 100x100 pixel area.
            spriteBatch.Draw(dotTexture, new Rectangle(100, 100, 200, 200), Color.GreenYellow);

            spriteBatch.End();


            // If you were to run the program now without the code below then all you get is a clear screen,
            // Because we have not drawn to the back buffer yet which is what is presented to the screen by the video card.

            // Ok so now lets start drawing from the rendertarget to the backbuffer.

            spriteBatch.Begin();

            // Set the drawing target to be the screens back buffer again.
            GraphicsDevice.SetRenderTarget(null);

            // Treat the render target buffer we drew to before as if it is just a regular texture.
            Texture2D tempTexture = (Texture2D)renderTargetIsAOffScreenBuffer;

            // Draw that texture to the back buffer.
            spriteBatch.Draw(tempTexture, new Rectangle(0, 0, 500, 500), Color.Green);

            // Since its just a texture now you can treat it like one. 
            // For example draw just a portion of it to the backbuffer getting the source rectangle pixels from the render target and setting them as a destination to the backbuffer.
            // take note that draw takes the first rectangle as the pixel destination and the second is the texel source rectangle.
            spriteBatch.Draw(tempTexture, new Rectangle(100, 100, 200, 200), new Rectangle(100, 100, 200, 200), Color.White);

            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

First of all I wanted to know what happened on the GPU when changing render targets. Reiti answered that perfectly. I wrote that code as an example. In practice there is no reason to do such a thing of course. I know how to use a render target.

My second question was since that we established that swapping render targets is an expensive operation then what should I do if I wanted to write to a render target once, then apply the changes such that they can be sampled in a call immediately after.

I phrased my second question as “Is there any other way of applying a render target than swapping it with something else?” and not “How do I use a render target?”