Kubajzz's picture

Getting started with VBOs

Hi,

I'm slowly discovering the world of OpenTK and I can already do all basic operations in the immediate mode. Now I want to learn using VBOs to improve the performance of my application.

I spent several hours searching the internet and the OpenTK website and I think I understand the principles of VBOs. However, I'm still not able to use them.

I understand that I need to initialize the buffers using GL.GenBuffers and GL.BindBuffers, then I need to fill them with data (or update the data) using GL.BufferData and finally I render the image using GL.DrawArrays... However, I'm not able to put it all together and the code examples I've found didn't help me.

Could somebody please give me a simple step-by-step tutorial on using VBOs? For example, if I have an array of floats that defines the vertices of a triangle and another array that defines its normals, how do I save all this data to the buffer? All the overloads of the GL.BufferData function are really confusing for me...


Comments

Comment viewing options

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

OpenGL object management mechanism rely on binding points. For example, you need to initialize a Texture object, (1st) you generate a handle via GenTextures function, (2nd) you bind it to a target of usage choice (3rd) you allocate memory with TexImageXD functions (4th) you upload image data via TexSubImageXD functions. Actually, OpenGL offers uploading data while allocation (i.e you may upload data via TexImageXD functions)

First of all, a buffer is an allocated memory block on GPU and the allocation routine (3) is BufferData function(with pointer set to null). So these code segments are semantically same except that one of them allocates CPU buffer and theother does the allocation on GPU, then you may upload your data (4) with BufferSubData function.

Now show case:

Let's assume you need a vertex buffer with position (3 floats) and texcoords (2 floats). Now i want to interleave the data for understanding of stride parameter for buffer functions (that is which i find most confusing). So in CPU memory i want series of floats such that:
pos0x pos0y pos0z tex0u tex0v pos1x pos1y pos1z tex1u tex1v pos2x ...
I can allocate a float array with size 5xVertexCount or I can declare a struct such as:

 struct Vertex
{
    public Vector3 Position;
    public Vector2 TexCoord;
 
    public static readonly SizeInBytes = Vector3.SizeInBytes + Vector2.SizeInBytes;
}

then I can allocate array of type Vertex[] with size VertexCount. Either way parameters for the BufferData or BufferSubData routines will not change, because OpenTK handles this for us (special thanks here)

Now I can start allocation:

Vertex[] vertices; // struct of choice 1: VertexStructure
float[] vertices; // struct of choice 2: series of floats
// initialize vertices here
// ...
 
int vertexBuffer;
 //Generate 1 buffer name
GL.GenBuffers(1, ref vertexBuffer);
//Bind to array buffer target for allocation
GL.BindBuffer(BufferTarget.ArrayBuffer); 
 
//option 1: allocate and update all data
GL.BufferData(BufferTarget.ArrayBuffer, Vertex.SizeInBytes * vertices.Length, vertices, BufferUsageHint.StaticDraw);
 
//option 2: allocate buffer then upload data later (you either do option1 or option 2)
GL.BufferData(BufferTarget.ArrayBuffer, Vertex.SizeInBytes * vertices.Length, IntPtr.Zero, BufferUsageHint.StaticDraw);
//since the upload starts from the beginning we set offset parameter to zero
GL.BufferSubData(BufferTarget.ArrayBuffer, 0, Vertex.SizeInBytes * vertices.Length. vertices);

Now lets assume you want to use this buffer for XPointer functions.
For VertexPointer function you need to set size parameter to 3 (you have 3 floats for position), type parameter set to float, stride parameter set to size of Vertex structure (Vertex.SizeInBytes), and pointer is an offset to your data in buffer which is zero for "position" (IntPtr.Zero). Stride parameter is an increment value for data reader such that reader points to a memory location for reading "position" data, after reading a "position" (unlike streams this pointer is still on start of the position) pointer steps by stride number of bytes. stride set to zero is a special case and it is treated as if stride is set to size of data type being read (for our case if we set stride to zero then 2nd position will be read from tex0u tex0v pos1x).
For TexCoordPointer function similarly size is set to 2, type is float, stride is again Vertex.SizeInBytes, but pointer is set to start of texcoord data which is 12 bytes off the start.

Mincus's picture

There's some good example code provided with OpenTK.

Check out OpenTK.Examples -> OpenGL -> 1.5
VertexBufferObject.cs shows you how to manage a static VBO and DynamicVBO.cs shows you how to manage a dynamic VBO.