【问题标题】:How to prevent GraphicsDevice from being disposed when applying new settings?应用新设置时如何防止 GraphicsDevice 被释放?
【发布时间】:2013-04-25 22:19:45
【问题描述】:

我的游戏窗口允许手动调整大小,这意味着它可以像任何其他普通窗口一样通过拖动其边缘来调整大小。游戏还使用了RenderTarget2D rt2d,在主 Draw 方法中设置了主渲染目标:GraphicsDevice.SetRenderTarget(rt2d),但在主函数末尾重置回null(默认渲染目标) Draw 方法,这让它有点混乱:这真的是问题的根源吗,在 Render Target 设置为rt2d 之间调整游戏窗口的大小,而不是重置为默认值?现在看起来很像。

主 Draw 方法中的代码应该始终将主渲染目标重置回 null,因此通常不会发生这种情况。

不过,调整游戏窗口大小的结果有时会导致GraphicsDevice.isDisposed 返回true,然后游戏在第一个SpriteBatch.End() 处抛出System.ObjectDisposedException。我发现有关此错误的帖子可以追溯到 XNA 的第一天,但​​没有很好的解释(也没有提及更改渲染目标,因此它也可能是这些海报问题的根源)。

现在我可以通过多次调用这个方法来触发这个错误:

graphics.PreferredBackBufferWidth = graphics.PreferredBackBufferWidth;
graphics.PreferredBackBufferHeight = graphics.PreferredBackBufferHeight;
graphics.ApplyChanges();

…在主要的 Draw 方法中使用以下几行:

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();

我的猜测是,如果在重置渲染目标之前调整大小发生,我应该中止帧绘制并重置渲染目标,但我仍然不确定这正是导致此问题的原因。

UPD:有 Window.ClientSizeChangedgraphics.PreparingDeviceSettings 事件,但即使它们触发,默认渲染目标似乎也无济于事。

我猜这不是“调整客户区大小和应用新图形设置之间的超时”之类的。这很可能是由非默认渲染目标引起的。

渲染目标大小可能与新屏幕大小不同,因为这在将图形设备尺寸更改为完全相同的值时也会引发异常。

UPD2:我只是尝试让全屏切换挂起的操作,将 F11 设置为 isFullscreenTogglePendingtrue 并在主要Update 方法的开头,它根本没有帮助。然后我发现之前的全屏模式也是从主更新方法切换的,只是不是在一开始,而是在输入更新方法的一半,所以它在主Update方法中的位置并不重要运行,它仍然会导致此错误。有趣的是,当抛出异常时,GraphicsDevice.isDisposedfalse


这是异常消息:

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: 

它位于主 Draw 调用中的 spriteBatch.End()

如何防止出现此错误?


可能相关的问题:

【问题讨论】:

  • 你做的有多小?
  • 800×480 和 1920×1080,来回。我知道存在这个问题,将首选高度设置为零会导致处置,我想在以某种方式应用新分辨率时可能会发生类似的事情。也许这是某种尺寸检查超时的延迟,应该将其设置为更大的间隔?
  • 您能否发布一个始终如一地重现错误的示例?
  • @borrillis 我更新了完成它的确切方法。
  • 使用默认 XNA 项目和上面的代码(按下键时),我无法重现此异常。还有什么我应该做的吗?重现此行为的最小样本会很好。

标签: c# xna objectdisposedexception


【解决方案1】:

两件事: 1. 我不熟悉渲染目标......但这也许会有所帮助?来自 MSDN:

“渲染目标表示显示内存的线性区域,通常驻留在显示卡的显示内存中。因此,在重置设备时必须重新创建 RenderTarget 对象。”

2。 除此之外,我曾经遇到过类似的问题。我在绘制调用结束时处理纹理。这会很好,除非我试图移动窗口。每隔一段时间,当我尝试移动游戏窗口时,就会发生 ObjectDisposed 异常(对于纹理)。我对推理的最佳猜测是更新线程和绘制线程会错位,即使只是短暂的片刻,并且纹理会在有机会重置之前再次调用。除了确保在尝试绘制之前没有处理对象之外,我从未找到停止效果的方法。

当然,我们的情况可能完全不相关,但作为一种可能的解决方法,只需添加一个标志,该标志将在窗口最近重新调整大小时停止任何绘制调用。

如果这不能解决问题,希望它有助于缩小问题所在。

【讨论】:

    【解决方案2】:

    您不应该在 Draw 调用中创建任何图形资源,就像在 RenderTarget2D 中所做的那样。首先,此类资源的创建速度很慢,对于GraphicsDevice,应该只创建一次。只有 Set 调用应该在 Draw 方法中,因为设置已经创建的资源要快得多,因为它们已经在图形设备内存中。

    你应该怎么做 - 将所有图形资源创建(包括 RenderTarget2D)移动到 LoadContent 调用中,并且只在 Draw 中保留 Set 方法。每当重新创建 GraphicsDevice 时(例如,在调整视口大小时),都会调用 LoadContent 方法。所以之前创建的所有资源也会被重新创建。

    【讨论】:

    • 我将 RenderTarget2D 创建放在 Draw 方法中,因为游戏允许更改其窗口大小,这意味着如果渲染目标突然与预期大小不同会导致错误,所以要避免它,它设置了每个主要的 Draw 调用。我现在要使用 LoadContent 进行测试。
    • 您是否尝试过将 RenderTarget2D 放置在 LoadContent 中?更改视口大小应触发 GraphicsDevice 重新创建本身应触发 LoadContent 方法调用。
    • 刚做了,在游戏第一次开始后就再也没有被调用过。
    • 确实,它在 2.0 版本中已更改 (great blog post about it)。 graphicsDevice.ApplyChanges() 调用在哪里?是在游戏循环中(更新)还是异步调用?
    • 在主 Update 方法中。无论如何,调整窗口大小时也会发生这种情况,所以我认为问题不在于方法的位置。
    【解决方案3】:

    我认为您的例外是因为您在 draw 方法处于活动状态时重新创建图形设备。您只应在游戏运行后更改更新方法中的设备设置。如果要更改分辨率,请将一些变量(如 bool)设置为 true,在 update 方法中检查该值并在那里应用新的分辨率。

    public class Game1 : Microsoft.Xna.Framework.Game
    {
        protected override void Update(GameTime gameTime)
        {
            if(resolutionChanged)
            {
                graphics.PreferredBackBufferHeight = userRequestedHeight;
                graphics.PreferredBackBufferWidth = userRequestedWidth;
                graphics.ApplyChanges();
            }
    
            // ...
        }
    
        // ...
    }
    

    我也同意 OpenMinded:永远不要在每帧的基础上创建资源。使用 GraphicsDevicerManagers PreparingDeviceSettings 事件。它会在图形设备被重置或重新创建时触发。

    【讨论】:

    • 这很有用,但仍然不能解决手动调整游戏窗口大小时导致同样异常的问题。
    • 你在哪里创建你的 spritebatch?您需要在设备重置时重新创建它。通常,如果您在 Game.LoadContent 函数中创建 spritebatch,XNA 会为您执行此操作。但是你向 OpenMinded 提到它再也不会被调用,这很奇怪。
    • 它再也没有被调用过,因为我使用的是最新版本的 XNA。这种行为很久以前就改变了。我在主要的 Initialize 方法中创建我的精灵批次。我不会再重新创建它们了。
    【解决方案4】:

    我知道已经过去了很多时间,但是在禁用提供 Bloom 效果的游戏组件后,用户连续 2 次调整窗口大小后,我在我们 8 年的游戏中一直遇到此错误。结果发现,在 Draw 结束时 Bloom 组件正在设置 GraphicsDevice.Texture[1] 并且它没有在 draw 结束时将其重置为 null。这导致在第一次调整大小后处理 GraphicsDevice.Texture[1],但仍设置在设备上。在第二次调整大小时,处理的纹理导致 GraphicsDevice 重置失败。在重置之前或在绘制bloom-component之后将Texture[1]设置为null可以解决问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-13
      • 1970-01-01
      • 2020-07-22
      相关资源
      最近更新 更多