Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent GraphicsDevice from being disposed when applying new settings?

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?
like image 509
user1306322 Avatar asked Apr 25 '13 22:04

user1306322


1 Answers

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.

like image 185
Colton Avatar answered Sep 28 '22 07:09

Colton