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

4

11

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:

Octofoil answered 25/4, 2013 at 22:19 Comment(16)
How small are you making it?Reyreyes
800×480 and 1920×1080, back and forth. I know there is this issue, where setting preferred height to zero results in disposal, I guess something like this may happen while applying new resolution somehow. Maybe it's some kind of latency with size check timeout, and it should be set to greater interval?Octofoil
Can you post a sample that reproduces the error consistently?Nunhood
@Nunhood I updated with the exact method that gets it done.Octofoil
Using a default XNA project and the code above (when a key was pressed), I was unable to reproduce this exception. Is there something else I ought to do? A minimal sample that reproduces this behavior would be good.Boysenberry
@Octofoil Where exactly are you placing these lines of code? How often are they getting called?Nunhood
Updated with more info and code.Octofoil
I cannot reproduce this with the code you added. I put the first three lines in Update() getting called every frame the mouse button is held down, made the window resizable, and put the rest in Draw(). No matter what I do, it doesn't crash. Are you sure it's the GraphicsDevice that's disposed? Errors that happen inside Begin() and End() may not show up until End(), perhaps something you are trying to draw got disposed. At a quick glance, xboxforums.create.msdn.com/forums/p/86840/536058.aspx looks like it might be relevant.Kelbee
@JordanTrudgett I'm not sure, I'm judging from the exceptions I get. Is there a way to get something disposed of by applying graphics settings while having the render target set to non-default value?Octofoil
@Octofoil Not that I know of. I think the problem is hidden somewhere else, and only surfacing after the fact, which is why a minimal bug-producing sample would be useful to solve this issue. Does the error say: ObjectDisposedException: Cannot access a disposed object. Object name: 'GraphicsDevice'?Kelbee
@JordanTrudgett added the exception message.Octofoil
Hmm... I don't think you should be creating a new RT every frame. Rather, you should recreate it uppon resizing your window. Though I have no idea whether this has anything to do with your crash. Also; I'm thinking there is some reference to GraphicsDevice in some resource you are using that might not be updated with the new GraphicsDevice (cus it gets disposed/recreated when resized?).Damascus
@Stig-RuneSkansgård unupdated reference? Are you sure this is even possible? Could you describe how it can be happening?Octofoil
@Octofoil are you using multiple display devices or anything different graphics-wise that could be causing the problem (since nobody else can reproduce it?) It could be an undocumented bug or something. Can you run the code on another system or computer and see that the error still occurs?Kelbee
@Octofoil I'm not sure if this can happen with GraphicsDevice, But let's say you have a class that has a reference to it. Then, in some other context, you dispose the GraphicsDevice, which would make this reference "IsDisposed == true". In the same context, Game.GraphicsDevice is newed again. Then you in practise have two graphicdevices; One disposed, and one not disposed. But I am pretty sure now, that this wouldn't normally happen in XNA...Damascus
@Stig-RuneSkansgård I'll try to get my hands on another desktop pc capable of running Hidef profile XNA apps to test this. I don't ever intentionally dispose of my graphics device, it happens all of a sudden during applying settings. It can take some time and varies from the first try to maybe a hundred sometimes.Octofoil
F
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.

Fears answered 16/5, 2013 at 21:38 Comment(0)
F
1

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.

Felipa answered 18/5, 2013 at 12:51 Comment(5)
I placed RenderTarget2D creation inside the Draw method because the game allows to change its window size, which means it will result in an error if the render target is suddenly of different size than expected, so to avoid it, it's set each main Draw call. I'm gonna run a test with LoadContent now.Octofoil
Have you tried placing RenderTarget2D inside LoadContent? Changing the viewport size should trigger GraphicsDevice recreation which itself should trigger LoadContent method call.Felipa
Just did and it doesn't get called ever again after the first time when the game starts.Octofoil
Indeed, it was changed in 2.0 version (great blog post about it). Where is located graphicsDevice.ApplyChanges() call? Is it inside game loop (Update) or is it invoked asynchronously?Felipa
Inside the main Update method. Anyway, this also happens when resizing the window, so I don't think the problem is in the method's location.Octofoil
M
1

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.

Morris answered 19/5, 2013 at 1:16 Comment(3)
This is useful, but it still doesn't solve the problem of causing the very same exception while resizing the game window manually.Octofoil
Where do you create your spritebatch? You need to recreate it on a device reset. Normally XNA does this for you if you create the spritebatch in the Game.LoadContent function. But you mentioned to OpenMinded that it is never called again, which is strange.Morris
It's never called again because I'm using the latest version of XNA. The behavior was changed long time ago. I'm creating my sprite batches in the main Initialize method. I am not recreating them ever again.Octofoil
B
0

I know a lot of time passed but i was hitting this error consistently on our 8 years old game after the user resized the window 2 times in a row, after a gamecomponent providing Bloom effect was disabled. Turned out the Bloom component at the end of the Draw was setting a GraphicsDevice.Texture[1] and it wasn't resetting it to null at the end of the draw. This caused after the first resize to have the GraphicsDevice.Texture[1] disposed, but still set on the device. On the second resize the disposed texture was causing the GraphicsDevice to fail the reset. Setting Texture[1] to null before the reset, or after drawing the bloom-component fixed the problem.

Bacterin answered 8/10, 2021 at 9:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.