2

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

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

我正在渲染纹理映射三角形。在使用三角形渲染每个对象之前,我将BasicEffect.Texture属性设置为已创建的Texture2D对象,并将BasicEffect.TextureEnabled属性设置为true. 我BasicEffect在每个调用之间应用了BasicEffect.CurrentTechnique.Passes[0].Apply() - 我知道我调用Apply()的次数是我应该调用的两倍,但是代码被包装在一个辅助类中,该类Apply()在任何属性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 的经验更丰富,所以很明显,幕后发生了一些事情,正在创建我不知道的非托管内存。

4

1 回答 1

0

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

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

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

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

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

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

我希望这有帮助!

于 2013-09-04T20:53:33.253 回答