XNA 4.0 / C中纹理相关的内存泄漏#

时间:2013-05-22 22:23:44

标签: c# memory-leaks xna directx texture2d

我在用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。我对OpenGL比DirectX更有经验,所以很明显在幕后发生的事情正在创造我不知道的非托管内存。

1 个答案:

答案 0 :(得分:0)

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

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

  • 垃圾收集器的工作速度不足以获取渲染函数中分配的所有RAM
  • 您的代码中的其他位置存在内存泄漏,而是显示在此处

再一次,如果我对你的代码知之甚少,很难弄清楚这里有什么。但就我个人而言,我有一些建议:

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

我给你的建议?如果您对OpenGL非常熟悉,而且您正在做的事情相当简单,那么我会查看OpenTK。它是一个瘦的链接器层,它将OpenGL和“端口”转换为C#。所有的命令都是完全相同的,你可以灵活地使用整个.NET库来获得额外的烦恼。

我希望这有帮助!