XNA 3.1 to 4.0 requires constant redraw or will display a purple screen
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 开发者_如何学Godoesn'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
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.
精彩评论