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

眉间皱痕 提交于 2019-12-12 10:35:39

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!