Game Development Community

FIX INSIDE: Critical: Memory Leak (re-produced in XNA Chopper Strike) Causes Slowdown

by Matthew Hoesterey · in Torque X 2D · 05/24/2009 (3:34 pm) · 9 replies

This is a 2.0 bug that has still not been fixed in 3.0.
Currently Torque has a huge memory leak. I'm guessing it's in the GUI or SceneLoader Code.


To reproduce:
1) Start XNA Chopper Strike.
2)Leave the game sit on the splash/Menu screen
3) Go get coffee come back in five min (it's a really extreme slowdown after this long)
4) Load the game

Notice how slow everything runs.

This leak stops once the first .txscene is loaded.

You can also view a memory readout by calling
System.GC.GetTotalMemory(true).ToString();

(edit: Further testing has revealed that the leak does not stop if you load a .txscene while the menus are still up.)
Thanks.

#1
05/25/2009 (2:09 pm)
Hey Matthew:

Do you have the sample VS2008 project that shows this problem?

I've like to investigate this issue using DotTrace or SciTech's memory profiler to see whats up so I can make the appopriate bug fixes to my local code.

If need be you can e-mail the solution / project to my e-mail: Lshink at rocketmail dot com.

Thanks!
#2
05/25/2009 (8:56 pm)
Thanks for taking a look.

Heres a link to a sample:

http://www.mhoesterey.com/demo/XnaChopperStrike_MemLeak.rar

I just used the torque example XNA chopper Strike to repro it (so my code wasn't a factor) the only change I made is replacing the player score text with this:
_playerScore.Text = System.GC.GetTotalMemory(true).ToString();

You'll notice the longer you leave it sit on the menu the more memory that is leaked.

I'm curious to try the memory profilers you referred to. I tried CLR but it seams to crash with Torque.

Thanks Again!
#3
05/26/2009 (5:01 pm)
Hey Matthew,

I think I found the issue.

Inside GarageGames.Torque.GUI.DrawUtil on line 362 replace the following:

RenderInstance ri = SceneRenderer.RenderManager.AllocateInstance();

with

RenderInstance ri = new RenderInstance();

It seems to have worked for me. Hopefully someone who knows more about Torque X can verify that this change will not effect anything else.

If your interested in the profilers, you can get the trial of Jetbrains's dotTrace here: http://www.jetbrains.com/profiler/

And a trial of SciTech's memory profiler here: http://memprofiler.com/download.aspx

I'd recomment SciTech for memory leaks and dotTrace for performance.

Let me know if that fix works for you.
#4
05/26/2009 (6:41 pm)
It seams to work! thanks for the help! It doesn't seam to effect anything adversely.

Thanks for the info on the profilers they should help me find these types of issues in the future :).

#5
07/07/2009 (6:10 am)
How do those of us without the source get this fix?
#6
07/07/2009 (7:07 am)
I'd suggest bugging GG. They will need to fix it on their end.
#7
11/17/2009 (11:55 pm)
I dont think this was fixed in the latest release also but a simpler and IMO better fix for this instead of doing a new RenderInstance() call
would be to at the end of the BitmapStretchSR function where the Allocate is being called would be to put a FreeInstance call

for pre 3.1.4.0 source
SceneRenderer.RenderManager.Instance.FreeInstance(ri);

for 3.1.4.0 source
SceneRenderer.RenderManager.FreeInstance(_workingRenderInstance);
#8
12/01/2009 (3:42 am)
From my research the problem seems to be introduced by the technique of deriving your menu screen from GUIBitmap. So the GUI project template in the 3.1.4 release and the examples in "The Complete Guide to Torque X" have memory leaks in the menus. The more bitmaps on the menu, the faster the leak. BTW, I used dotTrace to confirm this (thanks for the recommendation).

As mentioned, GarageGames.Torque.GUI.DrawUtil.BitmapStretchSR uses SceneRenderer.RenderManager.AllocateInstance() to create a new render instance for the GUIBitmap. AllocateInstance() adds the newly created RenderInstance to a List<RenderInstance> in SceneRenderer.RenderManager. The problem is that the list of allocations is only reset when a SceneGraph is rendered in GUISceneView.OnRender(). So a workaround that doesn't require an engine change is to use a GUISceneView as your root GUIControl, i.e. your object used with GUICanvas.Instance.SetContentControl(). The drawback to this is that you have more setup since you have to create a camera for the GUISceneView.

The engine's assumption that the root is a GUISceneView makes sense because you want to only reset the RenderInstances when you are done with the frame. You don't want every rendering of a GUIBitmap to cause the list to reset because there could be many nested GUIBitmap controls. So the reset is only done in the GUISceneView.

I am just learning about the RenderManager but it seems like it is using this internal list of RenderInstances so that it can reuse the objects in memory and prevent garbage collection. Changing BitmapStretchSR to not use AllocateInstance() would seem to cause garbage collection and impact performance.

Calling FreeInstance() seems to mark the RenderInstance as reusable so that AllocateInstance() will reuse it, but I think this would cause all the GUIBitmap objects to reuse the same RenderInstance. This sort of defeats the purpose of the internal list of RenderInstances.

I would love to hear thoughts from anyone about the RenderManger and RenderInstance. Is there a reason beyond preventing garbage collection that we have a list of RenderInstances? Do we do some post processing of these render instances for example? Should there be a RenderInstance for each GUIBitmap?
#9
12/01/2009 (11:56 am)
One reason that springs to mind is that creating RenderInstance instances isn't the fastest operation on the block and lots of them created in a single frame would cause performance issues. So if you can cache them for reuse later then you get a performance gain.