【问题标题】:Texture related memory leak in XNA 4.0 / C#XNA 4.0 / C# 中与纹理相关的内存泄漏
【发布时间】:2013-05-18 03:27:27
【问题描述】:

我在用 C# 编写的 XNA 4.0 应用程序中发现了内存泄漏。该程序需要运行很长时间(几天),但它会在几个小时内耗尽内存并崩溃。打开任务管理器并观察内存占用情况,每隔一秒就会为我的程序分配另外 20-30 KB 的内存,直到它用完为止。我相信当我设置BasicEffect.Texture 属性时会发生内存泄漏,因为那是最终引发OutOfMemory 异常的语句。

该程序有大约 300 个大 (512px) 纹理作为 Texture2D 对象存储在内存中。纹理不是正方形,甚至不是 2 的幂 - 例如可以是 512x431 - 一侧总是 512px。这些对象仅在初始化时创建,因此我相当确信它不是由动态创建/销毁 Texture2D 对象引起的。一些界面元素创建自己的纹理,但只在构造函数中创建,并且这些界面元素永远不会从程序中删除。

我正在渲染纹理映射的三角形。在使用三角形渲染每个对象之前,我将BasicEffect.Texture 属性设置为已创建的Texture2D 对象,并将BasicEffect.TextureEnabled 属性设置为true。我在每个调用之间使用BasicEffectBasicEffect.CurrentTechnique.Passes[0].Apply() - 我知道我调用Apply() 的次数是我应该调用的两倍,但是代码被包装在一个调用@987654331 的辅助类中@ 每当BasicEffect 的任何属性发生变化时。

我为整个应用程序使用了一个 BasicEffect 类,我更改了它的属性并在渲染对象时调用 Apply()

首先,更改BasicEffect.Texture 属性并多次调用Apply() 是否会导致内存泄漏? 其次,这是渲染具有不同纹理的三角形的正确方法吗?例如。使用单个 BasicEffect 并更新其属性?

此代码取自帮助程序类,因此我已删除所有绒毛,仅包含相关的 XNA 调用:

//single BasicEffect object for entire application
BasicEffect effect = new BasicEffect(graphicsDevice);

// loaded from file at initialization (before any Draw() is called)
Texture2D texture1 = new Texture2D("image1.jpg");
Texture2D texture2 = new Texture2D("image2.jpg");

// render object 1
if(effect.Texture != texture1) // effect.Texture eventually throws OutOfMemory exception
    effect.Texture = texture1;
effect.CurrentTechnique.Passes[0].Apply();
effect.TextureEnabled = true;
effect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices1, 0, numVertices1, indices1, 0, numTriangles1);

// render object 2
if(effect.Texture != texture2)
    effect.Texture = texture2;
effect.CurrentTechnique.Passes[0].Apply();
effect.TextureEnabled = true;
effect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices2, 0, numVertices2, indices2, 0, numTriangles2);

这是一个 XNA 应用程序,因此我每秒调用 60 次 Draw 方法,它会呈现我所有的各种界面元素。这意味着我可以每帧绘制 100-200 个纹理,并且绘制的纹理越多,内存耗尽的速度就越快,即使我没有在更新/绘制循环中的任何地方调用 new。与 DirectX 相比,我对 OpenGL 的经验更丰富,因此很明显,幕后发生了一些事情,正在创建我不知道的非托管内存。

【问题讨论】:

  • 你能给 Visual Studio 的项目提供这个问题吗?我没有什么想法可以尝试解决这个问题。
  • 不幸的是,该项目是基于研究的,并且太大而无法提取一小块,所以我无法分发它。
  • 不要试图猜测这里发生了什么。学习使用CLR Profiler 工具。它会准确地告诉你是什么导致内存被分配。
  • 实际上,您的代码看起来不错。我也会这样做。问题可能在 XNA 内部很深。如果找不到解决方案,可以尝试不时重新创建效果。绝对不是一个好的解决方案,但如果可行的话......
  • 正如科尔坎贝尔所说;使用 CLR 分析器。我想补充一点,从文件中加载纹理意味着你必须自己处理它们。如果不这样做,并且多次加载它们,您将遇到 OutOfMemoryException。

标签: c# memory-leaks xna directx texture2d


【解决方案1】:

我能给你的唯一建议是将你的纹理分组到图集中,而不是一个一个地分组。如果您每帧渲染大量纹理,它将加快您的渲染时间并减少 GPU 上的负载。这样做的原因是因为 GPU 不必经常交换纹理来渲染,这是一项昂贵的操作。我正在使用我对 OpenGL 的了解,但我的猜测是 XNA 基于 DirectX,并且我假设它们以类似的方式加载纹理(除非您使用可以使用 OpenGL 的 Monogame)

也就是说,您没有提供太多信息。内存泄漏可能来自纹理切换,但也可能来自其他地方。您的大部分内存都被纹理占用,这可能就是为什么您会在那里而不是其他地方发生崩溃的原因。我猜这里发生了一些事情:

  • 垃圾收集器工作速度不够快,无法拾取渲染函数内分配的所有 RAM
  • 您的代码中的其他地方存在内存泄漏,而是显示在此处

再一次,如果我对您的代码知之甚少,很难弄清楚这里有什么。但尽我所能,我有一些建议给你:

  • 运行您的代码并查看您是如何引用事物的。确保在类和结构中没有任何临时引用。如果您使用某些东西并将其传递给不同的类并稍后将其视为“丢弃”,则很有可能有人仍在持有该对象,从而阻止它被删除
  • 搜索解决方案中的所有“新”关键字。如果你有一些东西经常使用“new”关键字,这可能是一个巨大的内存泄漏,因为它在堆中创建了大量的对象。垃圾收集器应该捡起它们,但我不会说我非常信任垃圾收集器。最坏的情况是垃圾收集器出现的频率不足以处理这种内存泄漏。
  • 寻找减小纹理大小的方法。 Atlasing 是一种解决方案,可以减少将每个纹理打包到自己的 Texture2D 中的开销。这可能需要更多的工作,因为您必须在一个从同一文件交换纹理的系统中工作,但在您的情况下,这可能是值得的。
  • 如果您确信 XNA 中存在问题,请尝试称为 Monogame 的不同实现。它遵循与 XNA 完全相同的结构,但由社区维护。因此,您正在使用的库的内容已被重写,并且很有可能破坏您的堆的任何东西都已修复。

我对你的建议?如果您真的熟悉 OpenGL 并且您所做的事情相当简单,那么我会检查 OpenTK。它是一个瘦链接器层,它采用 OpenGL 并将其“移植”到 C# 中。所有命令都完全相同,您可以灵活地使用整个 .NET 库来处理所有额外的问题。

我希望这会有所帮助!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-19
    • 2013-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多