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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With