收录日期:2019/04/24 20:58:04 时间:2010-06-16 01:55:16 标签:c#,memory,xna

I'm starting with C# and XNA. In the "Update" method of the "Game" class I have this code:

t = Texture2D.FromFile( [...] ); //t is a 'Texture2D t;'

which loads small image. "Update" method is working like a loop, so this code is called many times in a second. Now, when I run my game, it takes 95MB of RAM and it goes slowly to about 130MB (due to the code I've posted, without this code it remains at 95MB), then goes immediately to about 100MB (garbare colletion?) and again goes slowly to 130MB, then immediately to 100MB and so on. So my first question:

  1. Can You explain why (how) it works like that?

I've found, that if I change the code to:

t.Dispose()
t = Texture2D.FromFile( [...] );

it works like that: first it takes 95MB and then goes slowly to about 101MB (due to the code) and remains at this level.

  1. I don't understand why it takes this 6MB (101-95)... ?

  2. I want to make it works like that: load image, release from memory, load image, release from memory and so on, so the program should always takes 95MB (it takes 95MB when image is loaded only once in previous method). Whats instructions should I use?

If it is important, the size of the image is about 10KB.

Thank You!

First of all you need to understand that what you are doing is pretty strange!

The "normal" way to load a texture is to use the content pipeline and the content manager.

If you really have to load a texture from file and not the content system - you should do this only once for each texture. You should use Texture2D.FromFile in Game.LoadContent, and call Dispose in Game.UnloadContent (normally the content manager would handle calling Dispose for you - but because you're not going through the content manager you have to call Dispose yourself).


The important thing to realise is that you're working with both managed and unmanaged resources here.

The memory being used by managed objects will be handled by the garbage collector - in this case each instance of Texture2D uses a tiny bit of managed memory. 99% of the time you don't need to worry about it - the garbage collector is really good at handling managed memory - that's its job!

What you do need to worry about is the unmanaged resources - in this case the texture memory being used by all these textures you're loading. Eventually the garbage collector will run, see all these unreferenced Texture2D objects and collect them. When it collects them it will finalize them which, like calling Dispose, will release the unmanaged resources.

But the garbage collector cannot "see" all the unmanaged resources these Texture2D objects are using. It doesn't know when those objects need to be released - you can do a much better job yourself. All you have to do is call Dispose on your textures when they are no longer needed.

It's because the garbage collector doesn't know about that 30MB of extra memory used by your textures, that it is being accumulated when you are not calling Dispose. In addition to that, what you cannot see when you look at the process memory usage, is all the memory on the GPU that those textures are using!

(The underlying implementation of Texture2D is that it holds a reference to a DirectX texture object - which itself is an unmanaged object using unmanaged main memory - which in turn handles the texture and its associated memory on the GPU. The managed part of a Texture2D object is only about 100-200 bytes - which stores the aforementioned reference and a cache of the texture's width, height, format and so on.)


Now - as for what you are trying to achieve. If you really have a need to load a texture every frame (this is highly unusual), and you then also no longer need the previous texture...

Well, first of all, calling Dispose on the unused texture each frame is a valid method. You will be depending on the garbage collector to clean up all the garbage this creates (the managed side of Texture2D objects) - this is fine on Windows but might kill your performance on Xbox. At least you won't be leaking unmanaged resources.

A better method, particularly if the texture is the same size each time, is to just keep using the same texture object. Then call Texture2D.SetData on it with the new texture data each frame.

(If your textures are different sizes, or you have more than one in use at a given time, you might need to implement something like a texture pool.)

Of course, LoadFile takes an actual file, SetData takes raw data. You'll have to implement the data conversion yourself. A good starting point might be this thread on the XNA forum.

The Update Method should not be used to load Textures because it's loaded a lot. In .net, objects are garbage collected, which means that you can't explicitly free an object (even .Dispose doesn't do that).

If the system is under a lot of pressure, GC may not run as often as it could.

In short: You are "leaking" thousands of Texture2Ds.

The proper way to load them is in one of the Initialization Events and store them in a class variable, Dictionary, List or whatever structure. Basically, load it only once and then reuse it.

If you have to load a texture on-demand, only load it once and store it in the List/Dictionary/Class Variable and again reuse it.

Edit: Your approach of "Load Image, Release, Load, Release" won't work in .net, simply because you can't explicitly free memory. You could call GC.Collect, but that a) doesn't guaratee it either and b) GC.Collect is incredibly slow.

Is there a specific reason why you have to reload the image 60 times a second? If you only need to reload it every second or so, then you can use elapsedGameTime to measure the time expired since the last .Update and if it's more than your threshold, Reload the image. (You need a Class Variable like "LastUpdated" against which you compare and which you update when you reload the image)

As Andrew Russell says, loading your image 60 times per second is not what you want to do. However, I would add: Don't use Texture2D.FromFile at all!

XNA provides a robust content pipeline for you -- use it!

  1. Add your image to your game project. XNA by default knows what to do with PNG, GIF, and JP(E)G image types. When you compile your project, XNA will also process your image into the XNA Binary (*.XNB) file format.
  2. In MyGame.LoadContent, load your image using myTexture = Content.Load<Texture2D>(@"My/Image/Folder/MyImageAssetName")

Content.Load will load the compiled XNB version of your image. You can also do things in the image properties in your project such as set a masking color (ex: all white pixels in your JPG will be transparent in your game), scale the image, and change the asset name. (The asset name defaults to the image file name, but you can change it in the properties, and the asset name is what you'll use for Content.Load.)

Content.Load also caches the things it loads, so that if for some reason you have to call Load on the same asset multiple times, you're not multiplying the memory you use.

The XNA content pipeline can be used for many other things as well as images, including 3D models, sound, and even your own classes.