6

For menus in my game, I draw them once to the screen, then only redraw if they've been deemed dirty. This is handled through a boolean set to true whenever the user performs an action that should cause a redraw, and then the draw loop will check that value before drawing the menu. This logic worked perfectly in 3.1, but in 4.0 the menu will flicker (drawn for 1 frame) then show a purple screen until drawn again.

I've created a very simple test game in 4.0 to demonstrate the issue shown below. You will notice that the screen just looks purple. If you remove the line setting _isDirty to false, you will see the cornflower blue background.

public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    bool _isDirty = true;

    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
    }
    protected override void Draw(GameTime gameTime)
    {
        if (_isDirty)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            _isDirty = false;
        }
        base.Draw(gameTime);
    }
}

How would I go about getting the behavior from XNA 3.1? I've seen several people mention PreserveContents, but that doesn't seem to have any effect in 4.0 unless I'm applying it incorrectly.

Mike Dearing
  • 101
  • 1
  • 5

3 Answers3

13

Here is a rough overview of what gets called in an XNA game:

Update
BeginDraw
Draw
EndDraw

The default implementation of EndDraw ultimately calls GraphicsDevice.Present. With double-buffering turned on, this swaps the back and front buffers.

So what is happening is:

  • First time around: you're drawing your scene to the back buffer and then letting XNA swap it to the front.

  • Second time around: you're drawing nothing, leaving the surface filled with the purple colour that DirectX initialises these surfaces to, and swapping that to the front!

  • Subsequent times: you're drawing nothing, so you'll see the display flicker between these two surfaces.

There are several ways to suppress drawing in XNA. I go over them in this answer to a similar question. As in that answer, I recommend you override BeginDraw, like so:

protected override bool BeginDraw()
{
    if(_isDirty)
        return base.BeginDraw();
    else
        return false;
}

When BeginDraw returns false, Draw and EndDraw (and so Present) will not be called that frame. Nothing will draw and the front/back buffers won't swap.

Community
  • 1
  • 1
Andrew Russell
  • 26,924
  • 7
  • 58
  • 104
  • 1
    Perfect! Thanks for taking the time to detail the inner workings of Draw, it's exactly what I needed. – Mike Dearing Nov 21 '10 at 05:14
  • Note: As I discovered [over here](http://stackoverflow.com/a/16910684/165500), XNA will actually actively mark a buffer as uninitialised (clears to dark purple) during `Present`, so the bit in my answer about flickering is wrong - although the solution is still the same. (Pentti's [answer](http://stackoverflow.com/a/6283242/165500) here has details on the mechanism, although I'd avoid turning it off with the sledgehammer that is reflection. Far more sensible to use the API properly, as per my answer.) – Andrew Russell Jun 04 '13 at 06:01
  • I am getting "The name '_isDirty' does not exist in the current context" error. Using XNA 4.0 and putting this method in the Game1 class. Any ideas? – Xonatron Feb 20 '14 at 15:43
  • Is there another way, potentially to copy the current on screen contents (from the last used buffer) to the new buffer about to be used each time? – Xonatron Feb 21 '14 at 02:59
  • `_isDirty` comes from the original question. It's a flag that indicates that whether the game's scene (the one that you manage) has changed and needs to be redrawn. – Andrew Russell Feb 21 '14 at 09:51
  • To get the same effect as copying the buffer, use a render target. Either use `PreserveContents` and reuse the target each frame. Or bounce the contents to a second render target on the next frame. (And it's best to ask new questions like that in an actual new question.) – Andrew Russell Feb 21 '14 at 09:53
2

I recently stumbled into this too; I do not think it's a problem with flipping buffers. That should be the same in 3.1 and 4.0. I looked at GraphicsDevice with ILSpy and found out this:

What has changed is that in 4.0, the current render target is first cleared in GraphicsDevice.Present() if a private flag is set. By default, this flag will be set initially (render target is clean). Drawing into the render target clears this flag (render target now dirty). After performing it's duty, GraphicsDevice.Present() again sets the flag (render target was "flushed out" to your window and is now again considered clean).

To get results from GraphicsDevice.Present(), you'll need to call Draw() and EndDraw() in strict sequence.

If you call EndDraw() without a prior call to Draw(), you will not get the contents of the render target (which gets cleared), only the purple screen instead.

Unfortunately, the flag is private and there's no clean way to get at it. You can use reflection to clear the flag like this, forcing the render target dirty without drawing anything into it:

graphicsDevice.GetType().GetField("lazyClearFlags", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(graphicsDevice, 0);

(graphicsDevice is your current GraphicsDevice instance)

Place the above line before your call to EndDraw(), and the behavior reverts to what it was in 3.1

  • Is there another way, potentially to copy the current on screen contents (from the last used buffer) to the new buffer about to be used each time? – Xonatron Feb 20 '14 at 15:52
0

In my particular case, I have a state machine that switches between states that are responsible for drawing. So when I transition between two game states, there is nothing to draw, and the screen flashes purple until the next game state is active.

What I did to solve it was simply, in the Update, if I have no state, call the SuppressDraw() method in the game object. This prevents drawing from taking place until the next Update takes place.

I suppose that there is nothing stopping you from always calling it in Update, except when you set an isDirty flag. But you would have to be prepared to handle other events/cases where the screen might get trashed.

Scott Forbes
  • 166
  • 2
  • 11