Using VBOs for dynamic geometry

Started by
10 comments, last by PunCrathod 9 years, 9 months ago

If the driver can see that it will stall the GPU when you update a buffer is already in use then, then it might allocate a new block of memory and use that as the new memory region for the buffer, and discard the old block of memory when the GPU's finished with it. Doing this, the driver can avoid the stall as no two operations are using the same memory, but there's no guarantee that the driver will do this, nor is the driver required to do this.

Advertisement

Thank you guys. You have helped me a lot so far.

VBOs are harder to understand than I had expected.

Nevertheless I tried to implement a triple buffered VBO. I used the glBufferData(..., 0) trick, which samoth described above. Its maybe not the fastest, but it should suffice for now.

Can you have a look at the code, whether its ok? Its C#, but it should be easy to understand for non C# programmers. Maybe its helpful for others who have similar problems.

First, initialization:


        uint[] VBO_IDs = new uint[3];
        int counter = 0;
        long CAPACITY = 0x800000; // 8 MB

private void LoadVBO()
        {
            GL.GenBuffers(3, VBO_IDs);
            ErrorCode code = GL.GetError();
            if (code != 0)
                throw new Exception(code.ToString());

            // create buffers
            GL.BindBuffer(BufferTarget.ArrayBuffer, VBO_IDs[0]);
            GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(CAPACITY), IntPtr.Zero, BufferUsageHint.StreamDraw);
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);

            GL.BindBuffer(BufferTarget.ArrayBuffer, VBO_IDs[1]);
            GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(CAPACITY), IntPtr.Zero, BufferUsageHint.StreamDraw);
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);

            GL.BindBuffer(BufferTarget.ArrayBuffer, VBO_IDs[2]);
            GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(CAPACITY), IntPtr.Zero, BufferUsageHint.StreamDraw);
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
        }

And rendering:


 Vertex3f[] mesharr = Mesh.ToArray;
                int bufferSize;
                GL.PushClientAttrib(ClientAttribMask.ClientVertexArrayBit);
                // Vertex Array Buffer
                {
                    // Bind current context to Array Buffer ID
                    GL.BindBuffer(BufferTarget.ArrayBuffer, VBO_IDs[counter]);

                    // Send data to buffer
                    GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(mesharr.Length * Vertex3f.Stride), mesharr, BufferUsageHint.StreamDraw);

                    // Validate that the buffer is the correct size
                    GL.GetBufferParameter(BufferTarget.ArrayBuffer, BufferParameterName.BufferSize, out bufferSize);
                    if (mesharr.Length * Vertex3f.Stride != bufferSize)
                        throw new ApplicationException("Vertex array not uploaded correctly");

                    // Set the Pointer to the current bound array describing how the data ia stored
                    GL.VertexPointer(3, VertexPointerType.Float, Vertex3f.Stride, IntPtr.Zero);

                    // Enable the client state so it will use this array buffer pointer
                    GL.EnableClientState(ArrayCap.VertexArray);
                    // Enable the client state so it will use this array buffer pointer
                    GL.EnableClientState(ArrayCap.NormalArray);
                    GL.NormalPointer(NormalPointerType.Float, Vertex3f.Stride, (IntPtr)(3 * sizeof(float)));

                    GL.DrawArrays(PrimitiveType.Triangles, 0, mesharr.Length);

                    //invalidate VBO
                    GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(CAPACITY), IntPtr.Zero, BufferUsageHint.StreamDraw);
                    GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
                }

                


                GL.PopClientAttrib();

                counter = counter == 2 ? 0 : counter+1; 

I benchmarked it: Unfortunately, there is no real difference between regarding performance when using 1 vs 3 VBOs.

When I render 34960 triangles with 3 VBOs (see code above), I get ~200 FPS.

When I render 34960 triangles with just 1 VBO, I get ~210 FPS.

You shouldn't need to explicitly invalidate the VBO you've just used after drawing.

There also won't necessarily be any performance gain by using double/triple/N buffering. If the GPU has finished all draw calls using the vertex buffer in the previous frame, before you update the same buffer in the current frame then you won't see any difference as the driver doesn't need to synchronize anything. Think of all your GL calls as submissions into a queue. The GPU may consume the items of the queue at a different rate than you submit them. So if the GPU consumes them faster than your CPU submissions, then no synchronization will ever be needed (as it seems to be in your case). It's only when the GPU consumes the items slower than your CPU submissions that it needs to synchronize.

You shouldn't need to explicitly invalidate the VBO you've just used after drawing.

You might not need (on some drivers), but it is the "correct" thing to do. Or, well, one of several correct things to do (this one is the "traditional" recipe as opposed to the more modern unsynchonized or persistent maping stuff). See Server-side multi buffering.

Explicitly using several buffers is what they call "Client-side muli buffering" in that article.

Unfortunately, there is no real difference between regarding performance when using 1 vs 3 VBOs.

You are using multi-buffering in both cases, only once it's explicit, client-side, and once it's happening invisibly in the server. Since you are using multi-buffering in either case, it's not surprising that there is no big difference.

You don't actually need to invalidate the VBO when using GL.BufferData(). Inputting new data already does everything you need. If you want to use the invalidating you need to use GL.MapBuffer(). More on the subject here -> http://www.opentk.com/doc/graphics/geometry/vertex-buffer-objects.

Also if you are rendering the buffer that you just updated then the potential speedup of using multiple buffers goes to waste seeing as the drawarrays has to wait for the datatransfer to complete before starting to do the actual rendering in wich case you might aswell use a single buffer. You need to update the data of the buffer that is going to be rendered next frame instead. Besides multibuffering VBO:s isn't usually going to give you much anyways as the bottleneck is most of the time somewhere else.

Most times when gfx programmers talk about double or triple buffering what they mean is that they have two or three "screens" to wich they do all the rendering and in case of double buffering they swap the buffers after all rendering to the current frame has been completed. And in triple buffering they swap the two background rendering buffers after rendering is finished and swap the currently not in use rendering buffer with the displayd buffer when the monitor has finished presenting the buffer.

Be careful of overoptimization. What you should do is set yourself a goal fps. And only start optimizing if you get below that fps. Anything above it shouldn't matter at all. If you want 60+ fps, you add a feature and your fps drops from 200 to 120 just shrug it off and continue adding the next feature. And always start with the easiest optimizations first as they are more likely to take less time to implement and over half the time it will get you above the target fps.

Edit: oh and before you start to optimize anything profile the damn thing thoroughly so you avoid using tens of hours optimizing the part that takes 0.01% of the actual process. Use http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx to measure the time it takes on "your" end of the process on different parts of the program. And GL.BeginQuery(QueryTarget.TimeElapsed,...); and GL.EndQuery(QueryTarget.TimeElapsed); to measure the time it takes for the driver and the gpu to perform the tasks that were issued between them.

This topic is closed to new replies.

Advertisement