adityatan's picture

OpenTK lagging due to thousands of points

Hi there:

I just plotted a thousand points in OpenTK window. I also have created a first-person camera on that window. As I tried to maneuver, the window lagged so bad.

Do you think the lagging is possibly due to my code, or is it due to the capacity of OpenTK? (Things were fine when I only plotted hundreds of cubes.)

FYI, I'm trying to make a stress-strain-force-displacement visualization of a pile. So, if OpenTK cannot handle my coordinates, I might as well just switch to another platform.

THank you for your help.

Aditya


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture
  • Which OS, video card and CPU are you using?
  • How are you rendering those points?
  • Are your drivers up to date?
  • Are you compiling your code in release mode?

Using VBOs, I have been able to render tens of millions of polygons at interactive framerates. A thousand points should not be a problem for a modern system, even using immediate mode (i.e. GL.Begin()-GL.End(), which happens to be the slowest rendering method).

adityatan's picture

Here's a piece of my code.

        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.Translate(0, 0, -500);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            GL.LoadMatrix(ref cameraMatrix);
            GL.Rotate(camera_rotation, 0, 1, 0); // Add the rotation
            ...
            TextReader rdr2 = File.OpenText(@"c:\Input Files\SampleS05.PLT");
            string line2;
 
            while ((line2 = rdr2.ReadLine()) != null)
            {
                string[] fields = line2.Split(new char[] { ' ' });
 
                if (fields.Count() > 5)
                {
                    i = i + 1;
 
                    a.Nodes.ElementAt(i).Coordinate[0] = Convert.ToDouble(fields[0]); // x coordinate
                    a.Nodes.ElementAt(i).Coordinate[1] = Convert.ToDouble(fields[1]); // y coordinate
                    a.Nodes.ElementAt(i).Coordinate[2] = Convert.ToDouble(fields[2]); // z coordinate
 
                    string coordinate0 = fields[0].ToString();
                    string coordinate1 = fields[1].ToString();
                    string coordinate2 = fields[2].ToString();
 
                    char[] coordinateX = coordinate0.ToCharArray();
                    char[] coordinateY = coordinate1.ToCharArray();
                    char[] coordinateZ = coordinate2.ToCharArray();
 
                    GL.Begin(BeginMode.Points);
 
                    for (int c1 = 0; c1 < coordinateX.Length; c1++)
                    {
                        for (int c2 = 0; c2 < coordinateY.Length; c2++)
                        {
                            for (int c3 = 0; c3 < coordinateZ.Length; c3++)
                            {
                                GL.Vertex3(coordinateX[c1], coordinateY[c2], coordinateZ[c3]);
                            }
                        }
                    }
 
                    GL.End();
 
                    ...

I bought my desktop last year; so, I think my devices are fine. Thanks for helping.

Aditya

the Fiddler's picture

Ok, you are loading a file, converting to/from strings, doubles and character arrays every single frame. This is going to be slow no matter the framework or implementation language.

Your best bet is to move this code to a setup function that is called once on startup (OnLoad event). Store the results of the setup function and have OnRenderFrame work on those results directly.

Moreover, do not convert to/from strings in performance-sensitive code. OpenGL works best if you pass floating point values directly.

A faster version of the code might look something like this:

// This should probably be a property of a.Nodes
List<Vector3> vertices = new List<Vector3>();
 
protected override void OnLoad(EventArgs e)
{
    // The using statement ensures TextReader.Dispose() is called when done
    using (TextReader rdr2 = File.OpenText(@"c:\Input Files\SampleS05.PLT"))
    {
        string line2;
 
        while ((line2 = rdr2.ReadLine()) != null)
        {
            string[] fields = line2.Split(new char[] { ' ' });
 
            if (fields.Count() > 5)
            {
                vertices.Add(
                    new Vector3(
                        Convert.ToSingle(fields[0]), // x coordinate
                        Convert.ToSingle(fields[1]), // y coordinate
                        Convert.ToSingle(fields[2]), // z coordinate
                    ));
            }
        }
    }
}
 
protected override void OnRenderFrame(FrameEventArgs e)
{
    GL.Begin(BeginMode.Points);
 
    foreach (var vertex in vertices)
    {
        GL.Vertex3(vertex);
    }
 
    GL.End();
}

Edit: some code fixes.

reibisch's picture

What Fiddler said.

Even if you have 10,000 PLT (tecplot?) files to read in, it's far better to cache those up-front and just change a pointer to which plot you want to display at a given time in a timer of some sort. 10,000 files * 1000 points * 3 floats * 4 bytes per float is still trivial to pre-cache.

Hortus Longus's picture
Quote:

... a stress-strain-force-displacement visualization of a pile.

Do you know what you say?

Sometimes I think, it is worth a consideration to divide OpenTK.Development into
A) themes which have to do with OpenTK, and
B) themes a la "Why can OpenTK not write @"Hello World"" (what has also to do with OpenTK :-) ), and/or
C) themes like: "Why not load thousands files in OnRenderFrame?", what means: "Microsoft has said, I am a programmer; well now I am a programmer, why not does OpenTK , what I want?".
Fiddler, you can stop correcting me here, I know what you must say, I know the sides and books too.
--
The most fundamental problem in software development is complexity. There is only one basic way of dealing with complexity: Divide and conquer.

adityatan's picture

All, thanks for the help.

@Hortus: I'm new to C#, OpenTK, and also English as a language. Sometimes my civil engineering world gives me difficulties with its jargons.

I will be back for more questions, for sure. Thanks again.

Aditya

c2woody's picture
Hortus Longus wrote:

Fiddler, you can stop correcting me here, I know what you must say, I know the sides and books too.

Where did he *start* correcting you? Do you know what you say, and if so, why?

And is it necessary to post a reply in such a rude manner?

reibisch's picture

I had very similar code sitting around, so I thought I'd offer a fairly complete solution.

class Results
{
	List<Zone> zones = null;
	int currentZone = 0;
 
	internal Results()
	{
	}
 
        // pass something like LoadFromFiles(@"c:\test\sample", ".plt") -- that should capture all files with names 'c:\test\sample00.plt' to 'c:\test\sample99.plt' (provided you're not missing a file)
	internal void LoadFromFiles(string path_filename, string extension)
	{
	    int i = 0;
	    string filename = "";
	    this.zones = new List<Zone>();
 
	    while (System.IO.File.Exists(filename = path_filename + String.Format("{0:D2}", i) + "." + extension))
	    {
		Zone zone = new Zone();
		zone.LoadFromFile(filename);
		this.zones.Add(zone);
	    }
	}
 
        // call AdvanceZone from a timer or similar
	internal void AdvanceZone()
	{
	    if (this.currentZone < this.zones.Count)
	    {
		this.currentZone++;
	    }
	}
 
        // set up your camera etc prior to this call
	internal void Render()
	{
	    this.zones[this.currentZone].Render();
	}
}
 
class Zone
{
	List<Vector3> vertices = null;
 
	internal Zone()
	{
	}
 
	internal void LoadFromFile(string filename)
	{
	    this.vertices = new List<Vector3>();
	    // The using statement ensures TextReader.Dispose() is called when done
	    using (System.IO.TextReader rdr2 = System.IO.File.OpenText(filename))
	    {
		string line2;
 
		while ((line2 = rdr2.ReadLine()) != null)
		{
		    string[] fields = line2.Split(new char[] { ' ' });
 
		    if (fields.Count() > 5)
		    {
			vertices.Add(
			    new Vector3(
				Convert.ToSingle(fields[0]), // x coordinate
				Convert.ToSingle(fields[1]), // y coordinate
				Convert.ToSingle(fields[2]) // z coordinate
			    ));
		    }
		}
	    }
	}
 
	internal void Render()
	{
	    GL.Begin(BeginMode.Points);
 
	    foreach (var vertex in vertices)
	    {
		GL.Vertex3(vertex);
	    }
 
	    GL.End();
	}
}

One catch if you're new to C#: don't try to modify the values of the Vector3s once you've added them to a list.

adityatan's picture

The solution does not give me the same plot as if I used my old method. Any idea why?

Aditya

reibisch's picture

You're still responsible for setting up your matrices prior to calling any of the render functions we've provided. If you've implemented Fiddler's solution, you'll need to include the following prior to the GL.Begin/End pair:

GL.MatrixMode(MatrixMode.Modelview);
GL.Translate(0, 0, -500);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.LoadMatrix(ref cameraMatrix);
GL.Rotate(camera_rotation, 0, 1, 0); // Add the rotation

A screen shot of how the two differ would be handy in troubleshooting.