问题
My game window has manual resizing allowed, which means it can be resized like any other normal window, by dragging its edges. The game also makes use of a RenderTarget2D rt2d
, to which the main Render Target is set in the main Draw method: GraphicsDevice.SetRenderTarget(rt2d)
, but it is reset back to null
(the default render target) in the end of the main Draw method, which makes it a little bit confusing: is this really the source of the problem, resizing the game window right between the moment Render Target is set to rt2d
, and not reset back to default? Right now it looks like it.
The code within the main Draw method is supposed to always reset the main Render Target back to null
, so there are no expected cases when this normally wouldn't happen.
Still, the result of resizing the game window sometimes causes GraphicsDevice.isDisposed
return true
, and then the game throws System.ObjectDisposedException
at the first SpriteBatch.End()
. I've found posts about this error going back to the first days of XNA, but without a good explanation (and also without mentioning changing Render Target, so it may have been the source of the problem for those posters too).
Right now I'm able to trigger this error by calling this method a few times:
graphics.PreferredBackBufferWidth = graphics.PreferredBackBufferWidth;
graphics.PreferredBackBufferHeight = graphics.PreferredBackBufferHeight;
graphics.ApplyChanges();
…With the following lines in the main Draw method:
RenderTarget2D rt2d = new RenderTarget2D(GraphicsDevice,
graphics.PreferredBackBufferWidth,
graphics.PreferredBackBufferHeight);
GraphicsDevice.SetRenderTarget(rt2d);
sb.Begin();
// main draw method here, it's pretty big, so it might be taking long
// enough to process to actually resize before resetting render target
sb.End();
GraphicsDevice.SetRenderTarget(null);
sb.Begin();
// draw the whole rt2d to the screen
sb.End();
My guess is that I should be aborting the frame draw and resetting the render target if the resizing happens before the Render Target is reset, but I'm still not sure this is exactly what is causing this.
UPD: There are Window.ClientSizeChanged
and graphics.PreparingDeviceSettings
events, but even when they fire, defaulting the render target doesn't seem to help.
I guess this is not "timeout between resizing the client area and new graphics settings applying" or whatever. This is most likely caused by non-default render target.
And it's probably not that the render target size becomes different from new screen size, because this also throws exception when changing graphics device dimensions to the exact same values.
UPD2: I just tried making fullscreen toggling a pending operation, making the F11 set isFullscreenTogglePending
to true and checking it in the beginning of the main Update
method, and it didn't help at all. Then I figured out that previously fullscreen mode was also being toggled from the main Update method, only not at the very beginning, but halfway through the input update method, so it doesn't really matter where in the main Update
method it is run, it still causes this error. Interestingly, the GraphicsDevice.isDisposed
is false when the exception is thrown.
This is the exception message:
System.ObjectDisposedException occurred
Message=Cannot access a disposed object.
Object name: 'GraphicsDevice'.
Source=Microsoft.Xna.Framework
ObjectName=GraphicsDevice
StackTrace:
at Microsoft.Xna.Framework.Helpers.CheckDisposed(Object obj, IntPtr pComPtr)
at Microsoft.Xna.Framework.Graphics.BlendState.Apply(GraphicsDevice device)
at Microsoft.Xna.Framework.Graphics.GraphicsDevice.set_BlendState(BlendState value)
at Microsoft.Xna.Framework.Graphics.SpriteBatch.SetRenderState()
at Microsoft.Xna.Framework.Graphics.SpriteBatch.End()
at secret_project.Game1.Draw(GameTime gameTime) in P:\msvs projects\secret_project\Game1.cs:line 3310
InnerException:
It's at the spriteBatch.End()
in the main Draw call.
How do I prevent this error?
Possibly related questions:
- When I change vertical size of XNA game window to minimum, it throws ObjectDisposedException for spritebatch, why?
- Is it possible to restore a GraphicsDevice if something goes wrong with it?
回答1:
Two things: 1. I'm not familiar with render targets... but maybe this will help? From MSDN:
"Render targets represent a linear area of display memory and usually reside in the display memory of the display card. Because of this, RenderTarget objects must be recreated when the device is reset."
2. Besides that, I had a similar problem at one point. I was disposing of a texture at the end of the draw call. This would work fine, unless I tried to move the window around. Every once in a while, an ObjectDisposed exception would occur (for the texture) when ever I tried to move the game window. My best guess at the reasoning was that the update thread and draw thread would get misaligned, if only for a brief moment, and the texture would be called again before it had a chance to reset. I never found a way to stop the effect, other than just making sure the object was not disposed before attempting to draw.
Of course, our situations might be completely unrelated, but as a possible fix, just add a flag that will stop any draw calls when the window has been recently re-sized.
And if that does not solve it, hopefully it will help narrow down what the problem is not.
回答2:
You should not create any graphics resources inside Draw call, like you did with RenderTarget2D
. First of all creating of such resources is slow and should be done only once for a GraphicsDevice
. Only Set calls should be inside the Draw
method, as setting already created resource is much faster since they are already inside graphics device memory.
What should you do - is to move all graphics resources creation (including RenderTarget2D
) inside LoadContent
call and leave only Set methods inside Draw
. The LoadContent
method is called whenever the GraphicsDevice
is recreated (for example, when you resizing the viewport). So all previously created resources will be recreated too.
回答3:
I think your exception comes because you are recreating the graphics device while the draw method is active. You should only change the device settings in the update method once your game is running. Set some variable like a bool to true if the resolution should be changed, check that value in the update method and apply the new resolution there.
public class Game1 : Microsoft.Xna.Framework.Game
{
protected override void Update(GameTime gameTime)
{
if(resolutionChanged)
{
graphics.PreferredBackBufferHeight = userRequestedHeight;
graphics.PreferredBackBufferWidth = userRequestedWidth;
graphics.ApplyChanges();
}
// ...
}
// ...
}
Also I aggree with OpenMinded: Never create a resource on a per frame basis. Use the GraphicsDevicerManagers PreparingDeviceSettings event. It will be fired when the graphicsdevice is reset or recreated.
来源:https://stackoverflow.com/questions/16225701/how-to-prevent-graphicsdevice-from-being-disposed-when-applying-new-settings