Dr_Asik's picture

Vertex pointers help

I'm trying to understand how to use vertex pointers instead of immediate mode to display my terrain. With vertex pointers however, the terrain gets cut off/corrupted after a few rows. Here's how it looks like when rendered in immediate mode (correct output):

Here's how it's rendered using vertex pointers:

The terrain is ~30K triangles, one very small texture applied to each triangle. One static directional light. I've isolated the two different drawing modes (immediate and vertex pointers) in two different methods that deal with exactly the same data. Here they are:

       void renderImmediate() {
            foreach (var texture in sortedTriangles.Keys) {
                GL.BindTexture(TextureTarget.Texture2D, (int)texture);
                var triangles = sortedTriangles[texture]._Triangles;
                var normals = sortedTriangles[texture]._Normals;
                var texCoords = sortedTriangles[texture]._TexCoords;
                GL.Begin(BeginMode.Triangles);
                for (int i = 0; i < triangles.Length; ++i) {
                    GL.Normal3(normals[3 * i]);
                    GL.TexCoord2(texCoords[3 * i]);
                    GL.Vertex3(triangles[i].P0);
                    GL.Normal3(normals[3 * i + 1]);
                    GL.TexCoord2(texCoords[3 * i + 1]);
                    GL.Vertex3(triangles[i].P1);
                    GL.Normal3(normals[3 * i + 2]);
                    GL.TexCoord2(texCoords[3 * i + 2]);
                    GL.Vertex3(triangles[i].P2);
                }
                GL.End();
            }
        }
 
        void renderWithVertexPointers() {
            GL.Color3(Color.White);
            GL.EnableClientState(ArrayCap.VertexArray);
            GL.EnableClientState(ArrayCap.NormalArray);
            GL.EnableClientState(ArrayCap.TextureCoordArray);
 
            foreach (var tileList in sortedTriangles) {
 
                GL.BindTexture(TextureTarget.Texture2D, (int)tileList.Key);
                GL.NormalPointer(NormalPointerType.Float, 0, tileList.Value._Normals);
                GL.TexCoordPointer(2, TexCoordPointerType.Float, 0, tileList.Value._TexCoords);
                GL.VertexPointer(3, VertexPointerType.Float, 0, tileList.Value._Triangles);
 
                GL.DrawArrays(BeginMode.Triangles, 0, tileList.Value._Triangles.Length);
            }
            GL.DisableClientState(ArrayCap.TextureCoordArray);
            GL.DisableClientState(ArrayCap.NormalArray);
            GL.DisableClientState(ArrayCap.VertexArray);
        }

... where sortedTriangles is a Dictionary<int, TileList> associating each texture with the triangles that use it (a TileList is a list of triangles, normals and texture coordinates).

Anyone knows what's going on? I had exactly the same results on my laptop's integrated graphics and on my HD4890.


Comments

Comment viewing options

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

Your vertex pointers are dangerous in that the .Net GC may relocate the TileLists while OpenGL is using them. This can result in crashes or graphics corruption such as the one you are seeing.

The solution is to load the data into vertex buffer objects and call DrawArrays on that (i.e. use server memory instead of client memory). Fortunately, this is a trivial modification (check the static VBO sample for working code).

You might be able to simplify things a bit by "interleaving" the normals, vertices and texcoords into a single list:

[StructLayout(LayoutKind.Sequential)]
struct VertexPositionNormalTexture
{
    public Vector3 Position;
    public Vector3 Normal;
    public Vector2 Texture;
}
var tileList = new List<VertexPositionNormalTexture>();
Dr_Asik's picture

EDIT: Ok nvm what I had previously written in this comment, I just found what my problem with vbos was. I was storing the indices as shorts but I have about 84000 indices for the terrain which is more than short.MaxValue.

So, next problem. I am now able to render the terrain correctly using one texture. I'd like to use several different textures. The problem is, when I try to render using multiple textures, only a few random fragments of the terrain get rendered. Here's my rendering code:

        void renderWithVBOs() {
            int i = 0;
            GL.InterleavedArrays(InterleavedArrayFormat.T2fN3fV3f, 0, (IntPtr)null);
            foreach (var terrain in sortedVertices.Keys) {
                GL.BindTexture(TextureTarget.Texture2D, (int)terrain);
                GL.BindBuffer(BufferTarget.ArrayBuffer, vbos[i++]);
                GL.BindBuffer(BufferTarget.ElementArrayBuffer, vbos[i++]);
                GL.DrawElements(BeginMode.Triangles, sortedVertices[terrain].Length, DrawElementsType.UnsignedInt, 0);
            }
        }

It works fine when there is only one type of terrain. With multiple textures, here's how it looks like:

I have verified that my data is correct by rendering the terrain using immediate mode, and it displays fine. I don't see what could be the problem.

Dr_Asik's picture

There is some notable improvement if I move GL.InterleavedArrays inside the loop, although I have no idea why, and it still present a lot of corruption.

        void renderWithVBOs() {
            int i = 0;
            foreach (var terrain in sortedVertices.Keys) {
                GL.InterleavedArrays(InterleavedArrayFormat.T2fN3fV3f, 0, (IntPtr)null);
                GL.BindTexture(TextureTarget.Texture2D, (int)terrain);
                GL.BindBuffer(BufferTarget.ArrayBuffer, vbos[i++]);
                GL.BindBuffer(BufferTarget.ElementArrayBuffer, vbos[i++]);
                GL.DrawElements(BeginMode.Triangles, sortedVertices[terrain].Length, DrawElementsType.UnsignedInt, 0);
            }
        }

Dr_Asik's picture

Another quick update. After scratching my head with disbelief, I made another shot in the dark and moved GL.InterleavedArrays just before the call to DrawElements. And, lo and behold:

It displays fine! (Yes it looks ugly and yes the textures are incorrect. But nonetheless that's how it's currently supposed to look like).

I'd like to understand what's going on. :S

Hortus Longus's picture

You can make a try and change
GL.DrawElements(BeginMode.Triangles, sortedVertices[terrain].Length, DrawElementsType.UnsignedInt, 0);
to
GL.DrawElements(BeginMode.Triangles, sortedVertices[terrain].Length, DrawElementsType.UnsignedInt, IntPtr.Zero);

martinsm's picture

Don't use GL.InterleavedArrays. Just setup your vertex pointers pointers manually, like you did before. And do it after binding VBO buffer, not before. By setting up pointers (either with GL.xxxPointer functions or with InterleavedArray) you are telling OpenGL at what offset in currently bound VBO buffer should it start reading data. If you setup pointers before binding VBO, it will use previously bounded VBO buffer.

For more optimization - use only one VBO buffer for whole terrain and use correct offset's for setting up pointers and drawing elements.

hannesh's picture

It is due to the garbarge collector.
When you move the call inside the loop or closer to the draw elements call, you're reducing the chance that the GC does something to it.

Note I said reducing the chance. You haven't eliminated the problem. On other machines, or when you have more going on in the scene, it is almost inevitable that this will happen again.

A solution would be to use VAO's. I'd be happy to help if you can't figure them out yourself.

martinsm's picture

hannesh: his final code is OK regarding garbage collector. It won't crash or display wrong output if GC will move something in middle.

And VAO won't help there. VAO just encapsulates vertex attribute pointer state into one object. It won't prevent GC from moving something (for example if you have set up VAO with client side buffer).

Dr_Asik's picture

I don't think it has anything to do with the GC. In the draw method, I don't pass any client side data to the gpu. My vbos have been uploaded to vram before already, and thus they are not managed by the .net runtime anymore. There is no chance of them getting garbage collected. In the draw method, I merely specify which vbo to use using their id.

I thought InterleavedArrays did the same thing as setting up the vertex pointer parameters manually.

martinsm's picture
Dr_Asik wrote:

I don't think it has anything to do with the GC.

That is correct.

Quote:

I thought InterleavedArrays did the same thing as setting up the vertex pointer parameters manually.

Yes, it does the same thing. Only InterleavedArrays are deprecated. But it shouldn't give any difference in rendering.
And directly setting vertex attribute pointers will give you more control and possibilities (argument types/sizes/offsets).