LRaiz's picture

Confused by GL.DrawRangeElements API

In my application I have multiple objects each one of them described as triangulation of a surface. Meshes whenever possible share vertices and normals and each surface has its own color. I want to display shaded picture using color materials and buffer arrays.
I generated buffers for vertices, normals, and indices. Then I prepare arrays of vertex coordinates and normals plus index array describing mesh triangles. For each surface I know index of a starting triangle vertex/normal plus total number of indices.

Below is my code that attempts to draw surfaces using data that is bound to Vertex, Normal, and Index buffers. This code works for the first surface but if there is more than 1 surface in the scene then 2nd, etc. surface is not drawn. I must be missing something about DrawRangeElements but can't figure out what. There are 7 commented out lines of code right after call to DrawRangeElements. These lines implement intended functionality in immediate mode and do work when uncommented. Can someone point out an error in a way how I call DrawRangeElements so I can do what I need without use of immediate mode.

 public void PaintSurfaces(Model model, IShowFilter paintFilter) {
            GL.PushClientAttrib(ClientAttribMask.ClientVertexArrayBit);
 
            GL.EnableClientState(ArrayCap.VertexArray);
            GL.BindBuffer(BufferTarget.ArrayBuffer, VtxBo);
            GL.VertexPointer(3, VertexPointerType.Float, 0, 0);
 
            GL.EnableClientState(ArrayCap.NormalArray);
            GL.BindBuffer(BufferTarget.ArrayBuffer, NrmBo);
            GL.NormalPointer(NormalPointerType.Float, 0, 0);
 
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, IdxBo);
            for (int ii = 0; ii < model.Surfaces.Count; ii++) {
                if (!paintFilter.ShowSurface(ii))
                    continue;
                Surface srf = model.Surfaces[ii];
                GL.Color3(srf.Color);
 
                // Get SrfVbo from the dictionary
                SrfVbo bo = _sIdxToSrfVbo[ii]; 
 
                // Obtained object "bo" contains start vertex index and total vertex count
                // The total number of triangles = count/3. Draw them 
                GL.DrawRangeElements(BeginMode.Triangles, bo.Start, bo.Start + bo.Count, bo.Count,
                                     DrawElementsType.UnsignedInt, new IntPtr(bo.Start * 3 * 4));
 
                // The code above does not work for second surface while the commented code below 
                // that should be equivalent works.
                //GL.Begin(BeginMode.Triangles);
                //for (int jj = bo.Start; jj < bo.Start + bo.Count; jj += 1) {
                //    uint kk = _indx[jj] * 3;
                //    GL.Normal3(_norm[kk], _norm[kk + 1], _norm[kk + 2]);
                //    GL.Vertex3(_coor[kk], _coor[kk + 1], _coor[kk + 2]);
                //}
                //GL.End();
            }
 
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
 
            GL.PopClientAttrib();
 
    }

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture
GL.DrawRangeElements(BeginMode.Triangles, bo.Start, bo.Start + bo.Count, bo.Count,
                                     DrawElementsType.UnsignedInt, new IntPtr(bo.Start * 3 * 4));

Is the 3 * 4 in last parameter correct?

In the code below you are using GL.Normal3 and GL.Vertex3, so maybe that should be 3 * 3?

LRaiz's picture

Normals and Vertices are bound to different buffers and they are synchronised as far as accessing by index is concerned. There are 3 vertices in one buffer and 3 normals in another buffer per triangle, thus a 3 multiplier. I also multiply by 4 because there are 4 bytes per unsigned int. I probably misunderstand the API but I assume that the intent to specify an offset into buffers expressed as char pointers.

LRaiz's picture

Things appear to start working if the call is modified to look like

GL.DrawRangeElements(BeginMode.Triangles, bo.Start, bo.Start + bo.Count, bo.Count,
                                     DrawElementsType.UnsignedInt, new IntPtr(bo.Start * 4));

Multiplier 4 accounts for the size of unsigned int.

The interface is a bit confusing because of redundant arguments. Arguments 1,2, and 3 are interdependent as well as arguments 1,4,and 5. Unfortunately documentation is not clear. Oh well.

the Fiddler's picture

Ah yes, I've been bitten by this before in GL.DrawElements.

The manpage fails to mention how indices is interpreted when an element buffer object is bound, and the 4.4 specification is equally terse. Annoying.

The only hint is found in the registry, where the DrawRangeElements is defined as:

        <command>
            <proto>void <name>glDrawRangeElements</name></proto>
            <param group="PrimitiveType"><ptype>GLenum</ptype> <name>mode</name></param>
            <param><ptype>GLuint</ptype> <name>start</name></param>
            <param><ptype>GLuint</ptype> <name>end</name></param>
            <param><ptype>GLsizei</ptype> <name>count</name></param>
            <param group="DrawElementsType"><ptype>GLenum</ptype> <name>type</name></param>
            <param len="COMPSIZE(count,type)">const void *<name>indices</name></param>
        </command>
 

len="COMPSIZE(count, type)" means that indices is interpreted according to parameters count and type (4 and 5). Mode (parameter 1) does not enter the equation.

It's unreasonable to expect users to dig through the xml registry in order to get this information, but it might be possible to add this to the documentation tooltips in OpenTK. I've filed an issue here: https://github.com/opentk/opentk/issues/15