Screenshot > NotSupportedException

Hi there,
for the paused game state I want the current game view been drawn in greyscale with the pause menu on top.
To avoid to unnecessarily draw all the layers and stuff of the game I thought it would be easier to create a screenshot of the current scene, save it as texture and draw it as background for the pause screen.

For a possible solution I found this here How to make screenshot?

Unfortunately I got an System.NotSupportedException at the screenshot = new RenderTarget()" line:

I call this right before setting the game state to paused:

if (inputHandler.gameplayButtons.Escape == ButtonState.Pressed && oldKeyState.IsKeyUp(Keys.Escape))
{
    screenshot = TakeScreenshot();
    curGameState = GameState.Paused;
}

Any Ideas why this happens?

Which platform are you targeting? It’s possible the graphics device doesn’t support many RenderTargets.

Regarding the implementation, I’ve done this exact same thing in my own game. An easy solution is to render your game to a RenderTarget, which you can then copy to another RenderTarget with a grayscale shader applied. Once that’s done, draw your pause menu on top and render the result to the backbuffer.

The benefit of rendering your game to a RenderTarget instead of directly to the backbuffer is you can use it in many ways to make your life easier, such as copying it, applying post-processing effects, and etc.

I am developing on Windows. I currently do not use any render targets, simply because there wasn’t a need so far. But it seesm that I should take a look at it ^^

Ok, I tried to work with different render targets. Problem is, the screen that is supposed to bei the pause background stays black. I guess there is something wrong with taking a copy of the actual rendertarget.
I’ll try to keep it as short as possible;

private RenderTarget2D renderTarget;
private RenderTarget2D screenshot;

protected override void Initialize()
{
    ...
    PresentationParameters pp = graphics.GraphicsDevice.PresentationParameters;
    renderTarget = new RenderTarget2D(graphics.GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight, false, SurfaceFormat.Color, DepthFormat.None, pp.MultiSampleCount, RenderTargetUsage.PreserveContents);
    ...
}

protected override void Update(GameTime gameTime)
{
    ...
    if (inputHandler.gameplayButtons.Escape == ButtonState.Pressed && oldKeyState.IsKeyUp(Keys.Escape))
    {
        screenshot = TakeScreenshot();
        curGameState = GameState.Paused;
    }
    ...
}

protected override void Draw(GameTime gameTime)
{
    graphics.GraphicsDevice.SetRenderTarget(renderTarget);
    ...

    if (curGameState == GameState.Paused)
    {
        spriteBatch.Begin();
        spriteBatch.Draw(screenshot, new Vector2(0, 0), Color.Gray); //Black
        UIManager.DrawPauseMenu(spriteBatch); //Drawn properly
        spriteBatch.End();
    }

    if(curGameState == GameState.Playing)
    {
       //DRAW ALL THE THINGS (map layers, characters, UI...)
    }

    graphics.GraphicsDevice.SetRenderTarget(null);
    spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque);
    spriteBatch.Draw((Texture2D)renderTarget, Vector2.Zero, Color.White); //this works fine
    spriteBatch.End();
}

public RenderTarget2D TakeScreenshot() //this method seems to be useless but I might fiddle more around later
{
    return renderTarget;
}

I am probably doing something stupid at some point, but yeah… lack of knowledge and experience with RenderTargets ^^

Right now, screenshot is a reference to the RenderTarget you drew on rather than a copy. When you bind a RenderTarget to the graphics device, it discards its contents by default, as it’s the most platform-compatible behavior. The start of your Draw method is currently clearing the screenshot each frame.

What you might be able to do is not set the RenderTarget to the graphics device when the game is paused. Though, it looks like you’re going for rendering everything, including the pause menu, to a RenderTarget; is this correct?

Another note: RenderTarget2D derives from Texture2D, so there shouldn’t be a need for an explicit conversion when passing it into Spritebatch.Draw.

yes. I am currently drawing the whole screen to one rendertarget, I fiddled around with different rendertargets depending on gamestate = paused or playing. the outcome was the same. Black background behind the menu.

My idea was to save the current view as texture and basically add an additional Draw-Call before I draw the pause menu itself.
It would be furthermore handy to have this saved texture because out of the pause menu you can enter a settings menu and the background should stay the same obviously.

In this case, what you want is this:

  1. Set the RenderTarget to renderTarget (as you’re doing now)
  2. Render everything as normal
  3. If curGameState is Playing, render everything, then copy renderTarget to screenshot by drawing to it.

In some (untested) code:

protected override void Initialize()
{
    ...
    PresentationParameters pp = graphics.GraphicsDevice.PresentationParameters;
    renderTarget = new RenderTarget2D(graphics.GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight, false, SurfaceFormat.Color, DepthFormat.None, pp.MultiSampleCount, RenderTargetUsage.DiscardContents);
    screenshot = new RenderTarget2D(graphics.GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight, false, SurfaceFormat.Color, DepthFormat.None, pp.MultiSampleCount, RenderTargetUsage.DiscardContents);
    ...
}

protected override void Draw(GameTime gameTime)
{
    graphics.GraphicsDevice.SetRenderTarget(renderTarget);
    ...
    
    if (curGameState == GameState.Paused)
    {
        spriteBatch.Begin();
        spriteBatch.Draw(screenshot, new Vector2(0, 0), Color.Gray); //Black
        UIManager.DrawPauseMenu(spriteBatch); //Drawn properly
        spriteBatch.End();
    }
    
    if(curGameState == GameState.Playing)
    {
    //DRAW ALL THE THINGS (map layers, characters, UI...)
    
    graphics.GraphicsDevice.SetRenderTarget(screenshot);
    spriteBatch.Begin();
    spriteBatch.Draw(renderTarget, Vector2.Zero, Color.White);
    spriteBatch.End();
    }
    
    graphics.GraphicsDevice.SetRenderTarget(null);
    spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque);
    spriteBatch.Draw(renderTarget, Vector2.Zero, Color.White); //this works fine
    spriteBatch.End();
}

Try this out first and see if it works. If you want to optimize it later, you can add a flag that copies renderTarget to screenshot only once upon entering the pause menu.

Keep in mind that tinting the texture gray is not equivalent to applying a grayscale effect; you’ll likely need a shader for that.

1 Like

Give this man a cookie!
This works perfectly! And it is actually quite simple once you understand it. Thank you very much.

And yes, I need to rework the tint to some real grayscale but for testing purposes it’s enough.
…And “screenshot” need to be renamed to “playingScreen” or so to make sense :smiley:

In case you’re intrested what it looks like:

Thanks again for your patience.

EDIT:
Result with proper greyscale effect

3 Likes