Nick Dodds
Nick Dodds

Reputation: 17

OpenGL: Draw Multiple Linestrips Efficiently

I'm new to openGL and I'm working on an application where I need to draw several linestrips each with several vertices. All-together, we're talking about 300,000 vertices.

In order to attempt to do this efficiently, I am using a VBO to store the vertex data.

I am using a single VBO for storing all the vertices for all the lines. I then use GLDrawElements to draw the entire VBO, passing it an index array which specifies which indices to draw, and leveraging primitiveRestart to designate where Linestrips start/end (see code below).

This does not execute as quickly I would've hoped. I can only render at about 10Hz. I'm thinking perhaps it's that I'm passing a large array of indices which must get copied to the GPU each render.

I'm having trouble determining the right direction to attempt to improve performance. Would shaders be the right way to go? Is there some way to NOT write the index array to the GPU during rendering (needed by DrawElements).

Any help determinig the right direction to explore would be appreciated.

I'm writing this in C# using openTK.

    //Enabled Primitive Restart
    GL.Enable(EnableCap.PrimitiveRestart);
    GL.PrimitiveRestartIndex(PrimitiveRestartIndex);

    //Generate Linestrip data to buffer (array of vertices)
    int[] FrameData = ScanBuffer.getBufferData();

    //Create the buffer on the GPU
    ScanBuffer.vbo_id = new int[1];
    GL.GenBuffers(1, ScanBuffer.vbo_id);

    //Buffer the data
    GL.BindBuffer(BufferTarget.ArrayBuffer, ScanBuffer.vbo_id[0]);
    GL.BufferData(BufferTarget.ArrayBuffer, new IntPtr(sizeof(int) * FrameData.Length), FrameData, BufferUsageHint.StaticDraw);
    GL.VertexPointer(2, VertexPointerType.Int, 0, 0);
    GL.EnableClientState(ArrayCap.VertexArray);

    //Generate the vertex index array
    uint[] IndexData = ScanBuffer.getIndexData();

    //Draw the VBO
    GL.BindBuffer(BufferTarget.ArrayBuffer, ScanBuffer.vbo_id[0]);
    GL.VertexPointer(2, VertexPointerType.Int, 0, 0);
    GL.DrawElements(BeginMode.LineStrip, IndexData.Length, DrawElementsType.UnsignedInt, IndexData);

UPDATE

I was able to find a viable solution using array element buffer to store my indicies. I get about 10X performance improvement. The buffer and render commands look as follows.

Buffer

    vertexData = generateVertexData();
    indexData = generateIndexData();

    //Bind to the VAO
    GL.BindVertexArray(vaoID[0]);

    //Bind + Write Index Data to the Buffer
    GL.BindBuffer(BufferTarget.ElementArrayBuffer, veoID[0]);
    GL.BufferData(BufferTarget.ElementArrayBuffer, new IntPtr(sizeof(uint) * IndexData.Count()), IndexData.ToArray(), BufferUsageHint.StaticDraw);

    //Bind + Write Scan Vertex Data to the Buffer
    GL.BindBuffer(BufferTarget.ArrayBuffer, vboID[0]);
    GL.BufferData(BufferTarget.ArrayBuffer, new IntPtr(sizeof(int) * VertexData.Count()), VertexData.ToArray(), BufferUsageHint.StaticDraw);
    GL.VertexPointer(2, VertexPointerType.Int, 0, 0);
    GL.EnableClientState(ArrayCap.VertexArray);

    //Remove Binding
    GL.BindVertexArray(0);
    GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
    GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);

RenderLoop

    //Enabled Primitive Restart
    GL.Enable(EnableCap.PrimitiveRestart);
    GL.PrimitiveRestartIndex(PrimitiveRestartIndex);

    GL.LineWidth(1);
    GL.Color3(Color.Red);
    GL.BindBuffer(BufferTarget.ArrayBuffer, vboID[0]);
    GL.VertexPointer(2, VertexPointerType.Int, 0, 0);

    //-Draw using a VEO-
    GL.BindBuffer(BufferTarget.ElementArrayBuffer, veoID[0]);
    GL.DrawElements(BeginMode.LineStrip, scanVertexBufferLength, DrawElementsType.UnsignedInt, 0);
    GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
    GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
    GL.BindVertexArray(0);

    GL.Disable(EnableCap.PrimitiveRestart);

Upvotes: 1

Views: 2005

Answers (1)

bernie
bernie

Reputation: 10390

Shaders might eventually help your performance, but they are definitely not the bottleneck at this point. Your rendering workload is very light and should be blazingly fast if done properly.

First of all, some of your usage of OpenGL is very outdated. Make sure you are following up to date documentation and tutorials to avoid this. I know it can be hard when entering in a new field to tell what is outdated or not. I suggest starting with a recently published OpenGL book if you can.

Now on to your code.

GL.BufferData(BufferTarget.ArrayBuffer, new IntPtr(sizeof(int) * FrameData.Length), FrameData, BufferUsageHint.StaticDraw);

This tells OpenGL to allocate a new buffer for your vertex data and this is done each time you render your geometry. This is obviously pretty bad for performance. What you want to do is allocate the buffer only once or at least only if it needs to grow. This call also uploads your 300000 vertex positions to the GPU each frame which is also costly. If the vertex positions do not change, upload them once and then reuse the buffer for the next frames. If the positions do change, use glBufferSubData() to upload the new data without reallocating your vertex buffer object's memory.

GL.DrawElements(BeginMode.LineStrip, IndexData.Length, DrawElementsType.UnsignedInt, IndexData);

This is the second big culprit of poor performance. In OpenGL "compatibility mode" which you must be using, glDrawElements() reads the vertex indices from the last parameter (IndexData) when no buffer (id 0) is bound to GL_ELEMENT_ARRAY_BUFFER (your case obviously). In essence, this does the same thing as described in the previous paragraph: allocate a new buffer and upload data each frame. You should create another buffer holding the indices, bind that to GL_ELEMENT_ARRAY_BUFFER and draw. This will avoid memory allocations and useless data transfers for reasons aforementioned.

Lastly, OpenGL is a state machine. Avoid redundant calls like GL.VertexPointer(2, VertexPointerType.Int, 0, 0);. To get peak performance, one usually needs to reduce the number of OpenGL function calls.

Upvotes: 1

Related Questions