• Advertisement
Sign in to follow this  

Managed DX / C# - Low On Virtual Memory

This topic is 4259 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello, I'm writing a program that as of now is pretty short and simple. I need it to run 24/7 for at least a couple days at a time. However, when it runs for a couple hours Windows tells me it's low on virtual memory and my program dissapears. When I run the program and watch the program in Windows processes I see the Mem Usage grow until it's gone. I thought C# was suppose to take care of memory managment for me so I'm not sure if I'm doing anything wrong when I keep creating new objects. Basically, this program creates 5 lines of text at the bottom of the screen and scrolls them across the screen. If anyone could help I would greatly appreciate it. Thanks. Data.cs
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Direct3D = Microsoft.DirectX.Direct3D;
using System.Xml;

class ForumMessage
{
    private string          sMessage;
    private string          sNewMessage;
    private bool            bVisible;
    private bool            bPaused;
    private int             iX, iY;
    private int             iRight, iBottom;
    private int             iWidth, iHeight;
    private int             iWindowWidth, iWindowHeight;
    private int             iSpeed;
    private Color           color;
    private Direct3D.Font   font;
    private Device          device;

    public ForumMessage(Device d, int iWindowWidth, int iWindowHeight, string sMessage)
    {
        this.device = d;
        this.iWindowWidth = iWindowWidth;
        this.iWindowHeight = iWindowHeight;

        sMessage    = sMessage;
        sNewMessage = sMessage;
        bVisible    = true;
        bPaused     = false;
        iSpeed      = 1;
        color       = Color.FromArgb(255, 0, 100, 0);
        iX          = iWindowWidth;

        Initialize();
    }

    public void Update()
    {
        iX -= iSpeed;
        iRight -= iSpeed;

        if (iRight < 0)
        {
            iX = this.iWindowWidth;
            Initialize();
        }
    }

    public void Draw()
    {
        font.DrawText(null, sMessage, new Rectangle(iX, iY, iRight, iBottom),
            DrawTextFormat.NoClip | DrawTextFormat.ExpandTabs,
            color);
    }

    public void Initialize()
    {
        font = null;

        Rectangle rect = new Rectangle();

        System.Drawing.Font localFont = new System.Drawing.Font("Courier New", 25.0f, FontStyle.Bold);
        font = new Direct3D.Font(device, localFont);

        sMessage = sNewMessage;

        rect = font.MeasureString(null, sNewMessage, DrawTextFormat.NoClip | DrawTextFormat.ExpandTabs | DrawTextFormat.WordBreak, Color.Black);

        iRight  = iX + rect.Right;
        iBottom = iY + rect.Bottom;
        iWidth  = iRight - iX;
        iHeight = iBottom - iY;

        localFont = null;
    }

    public void SetMessage(string s) { sNewMessage = s; }
    public void SetSpeed(int s) { iSpeed = s; }
    public void SetX(int x) { iX = x; }
    public void SetY(int y) { iY = y; }
    public string GetMessage() { return sMessage; }
    public int GetSpeed() { return iSpeed; }
    public int GetX() { return iX; }
    public int GetY() { return iY; }
    public int GetWidth() { return iWidth; }
    public int GetHeight() { return iHeight; }
    public int GetRight() { return iRight; }
    public int GetBottom() { return iBottom; }
    public void SetVisible(bool v) { bVisible = v; }
    public bool IsVisible() { return bVisible; }
    public void SetPaused(bool p) { bPaused = p; }
    public bool IsPaused() { return bPaused; }
    public void SetColor(Color c) { color = c; }

}



Form1.cs
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Direct3D = Microsoft.DirectX.Direct3D;
using System.Xml;

namespace ForumStats
{
	public class Stats : System.Windows.Forms.Form
	{		
        private Device device       = null;
        private Direct3D.Font font  = null;

		private XmlTextReader xmlStats;

        private ArrayList rgMessages;

        /// <summary>
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;

		public Stats()
		{
			//
			// Required for Windows Form Designer support
			//
			InitializeComponent();

            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);
		}

        /// <summary>
        /// We will initialize our graphics device here
        /// </summary>
        public void InitializeGraphics()
        {		
            // Set our presentation parameters
            PresentParameters presentParams = new PresentParameters();

            presentParams.Windowed = true;
            presentParams.SwapEffect = SwapEffect.Discard;
            presentParams.AutoDepthStencilFormat = DepthFormat.D16;
            presentParams.EnableAutoDepthStencil = true;

            // Create our device
            device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams);
            device.DeviceReset += new System.EventHandler(this.OnDeviceReset);
            OnDeviceReset(device, null);

            // What font do we want to use?
            System.Drawing.Font localFont = new System.Drawing.Font
                ("Courier New", 30.0f, FontStyle.Bold);

            // Create a font we can draw with
            font = new Direct3D.Font(device, localFont);
        }

        private void OnDeviceReset(object sender, EventArgs e)
        {
            Device dev = (Device)sender;

            dev.Lights[0].Type = LightType.Directional;
            dev.Lights[0].Diffuse = Color.White;
            dev.Lights[0].Direction = new Vector3(0, 0, 1);
            dev.Lights[0].Enabled = true;
        }


        protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
        {		

			device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.White, 1.0f, 0);
            device.BeginScene();

            // Draw Messages
            foreach (ForumMessage fm in rgMessages)
            {
                if (fm.IsVisible())
                    fm.Draw();
            }

            device.EndScene();
            device.Present();

            // Update Messages
            foreach (ForumMessage fm in rgMessages)
            {
                if (!fm.IsPaused())
                    fm.Update();
            }

            this.Invalidate();
        }

        /// <summary>
        /// Draw some text in screen coordinates
        /// </summary>
        private void Draw2DText(string text, int x, int y, Color c)
        {
            font.DrawText(null, text, new Rectangle(x, y, 
                this.Width , this.Height ),
                DrawTextFormat.NoClip | DrawTextFormat.ExpandTabs |
                DrawTextFormat.WordBreak , c);
        }

        /// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if (components != null) 
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		#region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
            // 
            // Stats
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
            this.ClientSize = new System.Drawing.Size(792, 573);
            this.Name = "Stats";
            this.Text = "Form1";

        }
		#endregion

		/// <summary>
		/// The main entry point for the application.
		/// </summary>
        static void Main() 
        {
            using (Stats frm = new Stats())
            {
                // Show our form and initialize our graphics engine
                frm.Show();
                frm.InitializeGraphics();
                frm.LoadStats();

                Application.Run(frm);
            }
        }

        private void LoadStats()
        {
            ForumMessage fm;

            //xmlStats    = new XmlTextReader("");
            rgMessages  = new ArrayList();

            for(int i = 0; i < 5; i++)
            {
                fm = new ForumMessage(device, this.ClientRectangle.Width, this.ClientRectangle.Height, "WASHINGTON - A Miami terror cell that dreamed of blowing up the Sears Tower and launching a \"full ground war\" against America asked Al Qaeda for machine guns, body armor, boots and $50,000 in cash. WASHINGTON - A Miami terror cell that dreamed of blowing up the Sears Tower and launching a \"full ground war\" against America asked Al Qaeda for machine guns, body armor, boots and $50,000 in cash. WASHINGTON - A Miami terror cell that dreamed of blowing up the Sears Tower and launching a \"full ground war\" against America asked Al Qaeda for machine guns, body armor, boots and $50,000 in cash. WASHINGTON - A Miami terror cell that dreamed of blowing up the Sears Tower and launching a \"full ground war\" against America asked Al Qaeda for machine guns, body armor, boots and $50,000 in cash.");
                fm.SetY(this.ClientRectangle.Height - fm.GetHeight() - (i * fm.GetHeight()));
                fm.SetSpeed(5 - i);
                fm.SetColor(Color.FromArgb(255 - (i * 50), 0, 100, 0));
                rgMessages.Add(fm);
            }            
        }
	}
}



Share this post


Link to post
Share on other sites
Advertisement
Try calling GC.Collect() somewhere in your render loop to get the garbage collector to do it's thing.

Share this post


Link to post
Share on other sites
Thanks for the reply.

I added it to my loop and I still seem to have the same problem =/ The mem usage just continues to grow. Maybe I'm calling it too much?

Here is a zip of my project and executables if you or anyone can find the time to take a look.

I would greatly appreciate it!

Thanks!

Share this post


Link to post
Share on other sites
Sounds like a bug to me since C# shouldn't let the heap get exhausted:
"Because destroying objects can be a time-consuming operation, the garbage collector destroys objects only when it is necessary (when the heap memory is exhausted) or when you explicitly ask it to (by calling the System.GC.Collect method). Clearly, this makes C# unsuitable for some time-critical applications."
garbage collection in c#
I had a program in C++ that would do the same thing but it was purposely written to deplete the heap and would close after several minutes of running with windows popping up it's low on virtual memory message.

Share this post


Link to post
Share on other sites
I think your problem is that you need to create a Sprite to draw the text on. By specifying null you cause a new sprite to be created each time you draw the text. I am afraid I cannot find the articles where I read all this but as I understand it, they add the event handlers to MDX objects to handle the device being reset etc. Having these event handlers means that the objects cannot be destroyed as there is a reference to them, thus you are effectively leaking sprite objects. I hope all this makes sense, it is not easy explaining stuff that you do not have a firm grasp on yourself.

This (fault with MDX IMO) is not really a problem if your objects are persistant, or if the program is short lived but in an app such as yours it will be bad.

Hope this helps

Share this post


Link to post
Share on other sites
Events in MDX can be disabled, IIRC, so that should be easy to check.

Share this post


Link to post
Share on other sites
Quote:
fault with MDX IMO


MDX does indeed introduce the need to manage your resources (and the memory they consume) manually. You should always make sure you're calling Dispose on any device bound resources if you no longer need them. Also as a general rule of thumb, it's always cheaper (in terms of performance and mem usage) to re-use exisiting objects instead of creating new ones. This also has the added benefit that you don't need to worry about memory management as much, because you're simply not consuming additional memory.

Be that as it may, that the program is running low on virtual memory is more an indication that there is something wrong with your plain C# objects not getting finilized, instead of any MDX resources. In my experience any memory leaks on these device bound resources typically lead to an OUT_OF_VIDEO_MEMORY exception long before RAM usage should become a problem.

Quote:
By specifying null you cause a new sprite to be created each time you draw the text.


I've changed the project to use a single sprite for all the text rendering (just passing this into the ForumMessage.Draw method as a parameter and using it instead of null in font.DrawText) and it seemed to solve the memory usage problem. When using the null parameter for the sprite, the app would allocate about 20KB extra RAM per second. When using the explicit sprite object, the ram usage stays constant. You can check this in the Windows Task Manager on the Processes tab.

Regardless, you might also want to look into some other things in the ForumMessage class. Specifically the redundant creation of D3DX font objects, not disposing the localFont object and recreating new rectangles all the time.



Edited:

After some more testing, it turned out the memory consumption is in fact less with the explicit sprite object, but the application keeps eating up memory, even with the rendering loop cut down to this:


protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Black, 1.0f, 0);
device.BeginScene();

device.EndScene();
device.Present();

this.Invalidate();
}


This is probably caused by all the invalidate calls that are queueing up messages on the Form's message queue. I've tried fiddling with Application.DoEvents() to force the app to handle the events, but this didn't work out either. I'd recommend using the AppStillIdle render loop instead.

[Edited by - remigius on June 26, 2006 5:01:40 PM]

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement