archangelwar's picture

GLControl context and strange tesselator behavior

I have been using the GLU tesselator implementation of OpenTK for a while now, and I have been experiencing some strange problems. Every once in a while, the tesselation results are visually... chaotic. It seems that a triangle or two is returning with the wrong vertices. Sorry I do not have any screenshots at the moment, but I will try and get some when it goes haywire again.

What is really strange about it is that it only seems to do this the first run after a compilation. I does not happen every time, but that is the only thing I have seen in common with when it screws up and when it doesn't.

There are only a few guesses that I have. First of all, I am simply creating a new GLControl right before I use the tesselator. So it might be possible that the context does not exist before I start tesselating. Since I am just creating a new control in code and not using it, how would I test to see if the context is active? The construction and initialization of the GLControl seems to be asynchronous (I am not sure about this though), is this true? Is there any other, more simpler way to get a GL context to keep the tesselator alive?

The only other thing I can think of is that it is some strange artifact of the JIT.

Can anyone think of what might be going on here?


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture

[GLU tesselators]
GLU tesselators have an ugly history. Between Tao and OpenTK, noone has managed to get them working reliably (although there is high demand). For OpenTK I wished to have a fully managed solution and avoid GLU tesselators entirely (maybe a C# port of the Mesa3d GLU tesselators) - unfortunately I didn't know when I'd have the time to implement something like this (it's no easy task, unfortunately), so I had to give in and add support.

However my pinvoke-fu was not good enough to make the callback mechanism marshal data reliably. Known caveats: does not work on Mono; requires unsafe code.

My guess is that you are seeing the results of memory corruption. Which is not good. Even worse, there's no clean solution at this point.

Someone produced a patch on the Tao boards that seems to fix some tesselator problems. This patch changes the callback declarations to:
public delegate void TessCombineCallback([MarshalAs(UnmanagedType.LPArray, SizeConst = 3)] double[] coordinates, [MarshalAs(UnmanagedType.LPArray, SizeConst = 4)] IntPtr[] vertexData, [MarshalAs(UnmanagedType.LPArray, SizeConst = 4)] float[] weight, [Out] IntPtr[] outData);
and
public delegate void TessVertexCallback([MarshalAs(UnmanagedType.LPArray, SizeConst = 3)][In] double[] vertexData);
which avoid the need for unmanaged code. I haven't tested the patch yet, but it might prove useful.

[OpenGL context]
The GLControl constructor calls CreateControl on itself, to force the creation to complete even when it is hidden.

That said, you should use GraphicsContext.CurrentContext to see if it is safe to call OpenGL functions:

if (GraphicsContext.CurrentContext != null)
{
}
archangelwar's picture

Thanks for the help Fiddler. I really need to get this working reliably, and I really, really do not wish to have to write my own tesselator (as the only algorithm I know that works is horrendously slow and is completely useless for my project). Do you know of any managed tesselators (with decent performance) that are our there that are open source or free to use and distribute? I have searched high and low, and the answers have all pointed at OpenTK. The main problem is that most "tesselators" are actually just "triangulators" and they just triangulate shapes instead of creating custom geometry from polygon data. Thus, they don't handle holes.

the Fiddler's picture

Don't worry, there are several solutions that don't included writing your own tesselator.

First, two managed programs with tesselators (I've never tested these, but either should work fine):

- The first one is contained in AGG#. It is based on Mesa3d.

- The other one is mentioned on the Tao forums (full source code contained in this post).

Your third option is to write a simple C program (dll or shared object) to handle the actual callbacks (using GLU internally). The dll will present a simple interface to be used by .Net (as simple as a single function taking an array of data and returning an array with the results).

The latter option is widely used and recommended for complex interfaces. However, the first two should be easier to use and maintain.

archangelwar's picture

Yeah, I have looked at the AGG# implementation but the project structure and the examples do not compile with the latest SVN version (the latest release version does not even have the tesselator) and it does not seem that work is continuing on a new release version that I can tell. There is no documentation and no forum, or anything that I can find.

I will review the full source on the Tao forums, but it is proving difficult to reconstruct into a usable form.

I will probably have to go with the third option.

Is the GLU.tesselator in its native implementation pretty much perfect?

the Fiddler's picture

I don't know if it's perfect, but I've never had any problems using the native implementation(s).

archangelwar's picture

I am going to go ahead and show you what I am doing with the tessellator in case a second pair of eyes can see something that I am not. Also, because of the mixture of safe and unsafe code, I might be unintentionally doing something wrong. I have done a few data dumps on the data passed back by the tessellator, and the "bad" tessellations seem to be somewhat consistant, and comparing them with a correct tessellation reveals that it is simply substituting the correct index data with an incorrect value. Also, I am getting an exception from the GLControl that I am a bit unsure of and thought I might pass that along as well.

Basically, what I am doing is simple:
1) Create new GLControl() (I am not actually using the GLControl, I just need the gl context)
2) Create new GLU.Tesselator
3) Assigning callbacks
4) Begin the tesselation
5) Pass in all vertices
6) End the tesselation
7) Collecting the index data passed in the vertex functions to create a vertex and index array
8) Dispose everything and unmarshal marshaled memory
9) Convert to Direct3D buffers
10) Render

Since I am not really doing anything other than collecting the vertex/index data into lists, the only functions performing any work are the Glu.TessVertex() and the Vertex callback (I have implemented the combine callback and it "should" work correctly, but in all my of test cases, it is never called, so I am not going to worry about it).

I wrapped the TessVertex function so that I will store the vertex into a list when I pass it and assign it an index. Then I send the vertex data + index data to the function in a double[4].

        public void AddVertex(Vector3 vert)
        {
            Vector4 objNewVert = new Vector4(vert.X, vert.Y, vert.Z, (float)_iIndex);
            _objVertices.Add(objNewVert);
            double[] adInput = new double[4];
 
            adInput[0] = (double)vert.X;
            adInput[1] = (double)vert.Y;
            adInput[2] = (double)vert.Z;
            adInput[3] = (double)_iIndex;
 
 
            //Marshal.WriteInt32(blah, index);
            //Console.WriteLine("THE VALUE PASSED HERE IS " + index.ToString());
 
            Glu.TessVertex(_objTess, adInput, adInput);
            _iIndex++;
 
        }

And then the callback disassembles the data to get the index back out and puts it in a list, thus forming the index buffer:

private void VertexHandler(IntPtr v)
        {
            uint iValue; // All we want from the passed data is the index of the vertex
 
            // First we cast the passed value as a "vertex + index" structure
            // that we used originally when passing in the vertex data
 
            //double[] adData;
 
            unsafe
            {
                double* objX = (double*)v;
                iValue = (uint)objX[3];
            }
 
            // Append the index value to our list of indices
            _objIndices.Add(iValue);
        }

Also, I have implemented an empty EdgeFlag callback so that I get a triangle list instead of a strip.

Perhaps my little unsafe section is breaking it? I am not used to jumping in and out of managed code (I was primarily a C++ programmer, and just recently picked up C#, so I am not used to all the nuances that differentiate the two).

And for the other issue with the GLControl(), I am getting an "OutOfMemoryException" from the control (but testing memory usage and so forth says that there is plenty of memory), and the inner exception states that it errored attempting to create a new window handle. Then, the innermost exception statest that "Object reference not set to an instance of an object" here:

Message="Object reference not set to an instance of an object."
Source="OpenTK"
StackTrace:
at OpenTK.Platform.Windows.Wgl.Arb.SupportsExtension(WinGLContext context, String ext)
at OpenTK.Platform.Windows.WinGLContext.OpenTK.Graphics.IGraphicsContextInternal.LoadAll()
at OpenTK.Graphics.GraphicsContext.OpenTK.Graphics.IGraphicsContextInternal.LoadAll()
at OpenTK.GLControl.OnHandleCreated(EventArgs e)
at System.Windows.Forms.Control.WmCreate(Message& m)

That is a snippet of the trace. This happens when I quickly create and destroy a control (roughly 10 seconds from New to Dispose). I have a Dispose method in my Tesselator class that calls Glu.DeleteTess and GLControl.Dispose() on the respective objects created in the constructor.

Any ideas what could be causing that and how I might avoid it?

Thanks for putting up with me.

archangelwar's picture

In fact, I just checked the diff between the two data dumps (a good tesselation and a bad one), and it seems that the bad one has 0 as the index value and it seems to be 0 for every instance of that vertex in the index list (for instance, every time there is a 3 as the index in a "good" tessellation, it is 0 in the bad one). I will see if I can check to see if the vertex data is also incorrect or just that index.