
Advanced data handling
We basically used OpenGL buffers to store data for quite some time. There are more interesting ways to manipulate buffers, as well as other interesting methods of transferring large amounts of data to shaders using textures. In this guide, we will discuss some of the more interesting features of the buffer and how we can use texture objects to store large amounts of data (the texture part of the lesson has not yet been written).
A buffer in OpenGL is an object that controls a certain part of the memory, and nothing more. We attach the value to the buffer by linking it to a specific target buffer. A buffer is just a buffer of a vertex array when we bind it to
GL_ARRAY_BUFFER , but we can also easily associate it with
GL_ELEMENT_ARRAY_BUFFER . OpenGL stores a buffer for each target and, based on the target, processes the buffers differently.
So far, we have filled the memory managed by the buffer objects by calling
glBufferData , which allocates a portion of the memory and adds data to this memory. If we passed
NULL as the data argument, the function would allocate memory and not fill it. This is useful if we want to reserve a specific part of the memory first, and in the future return to this buffer in order to gradually fill it.
Instead of filling the entire buffer with a single function call, we can also fill in specific buffer areas by calling
glBufferSubData . This function takes as arguments the target buffer, the offset, the size of the data, and the data itself. What's new in this function is that we can set an offset that indicates where we want to start filling the buffer. This allows us to insert / update only certain parts of the buffer memory. Note that the buffer must have enough allocated memory, so the call to the
glBufferData function
must always be before the call to
glBufferSubData .
glBufferSubData(GL_ARRAY_BUFFER, 24, sizeof(data), &data);
Another way to transfer data to the buffer is to find out the pointer to the buffer memory, and directly copy the data to the buffer yourself. When you call the
glMapBuffer function
, OpenGL returns a pointer to the memory of the current associated buffer to work with it:
float data[] = { 0.5f, 1.0f, -0.35f ... }; glBindBuffer(GL_ARRAY_BUFFER, buffer);
OpenGL knows that we are done with the pointer as soon as we tell it to it using the
glUnmapBuffer function. After executing this function, the pointer becomes invalid, and the function returns
GL_TRUE if OpenGL successfully placed your data in the buffer.
Using
glMapBuffer is useful for directly storing data in a buffer without first being stored in temporary memory.
Attributes of grouped vertices
Using
glVertexAttribPointer , we could specify the location of the contents of the vertex array buffer. In the vertex array buffer, we alternated attributes, i.e. we placed the position, normal and / or texture coordinates in order for each vertex. Now that we know a little more about buffers, we can use a different approach.
So now we can pack all the vector data into large pieces for each attribute type, instead of alternating them. Instead of a layout of alternating data 123123123123, we get the batch approach 111122223333.
When you load vertex data from a file, you usually get an array of positions, an array of normals, and / or an array of texture coordinates. It may be worth some effort to connect these arrays into a large array of alternating data. Using the batch approach is an easy solution that we can implement using the
glBufferSubData function:
float positions[] = { ... }; float normals[] = { ... }; float tex[] = { ... };
This approach allows you to directly send an array of attributes to the buffer as a whole, without preprocessing. We can also combine them into one large array and fill it immediately using
glBufferData , but using
glBufferSubData is great for such tasks.
We will also need to update the pointers to the attributes of the vertices to reflect these changes:
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)(sizeof(positions))); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)(sizeof(positions) + sizeof(normals)));
Note that the
stride parameter is equal to the size of the vertex attribute, since the next attribute of the vertex attribute can be found immediately after its 3 (or 2) components.
This gives us another approach to setting and defining vertex attributes. Using both approaches does not bring immediate benefits to OpenGL, basically it’s a more organized way to set vertex attributes. The approach you want to use is based solely on your preferences and application type.
Copy buffers
When your buffers are filled with your data, you may want to share this data with other buffers or perhaps copy the contents of the buffer to another. The glCopyBufferSubData function allows you to copy data from one buffer to another with relative ease. The function prototype looks like this:
void glCopyBufferSubData(GLenum readtarget, GLenum writetarget, GLintptr readoffset, GLintptr writeoffset, GLsizeiptr size);
The
readtarget and
writetarget arguments take target buffer values that we want to copy from and to where. For example, we could copy the data from the
VERTEX_ARRAY_BUFFER buffer to the
VERTEX_ELEMENT_ARRAY_BUFFER buffer, specifying these buffer targets as read and write targets, respectively. Then the buffers that are tied to these targets will be affected.
But what if we want to read and write data in two different buffers, which are the vertex array buffers? We cannot simultaneously bind two buffers to the same target buffer. For this reason, and only for it, OpenGL gives us two more storage targets, called
GL_COPY_READ_BUFFER and
GL_COPY_WRITE_BUFFER . Then we associate the selected buffers with these new target buffers and set these targets as
readtarget and
writetarget arguments .
glCopyBufferSubData reads data of a given size from the given
readoffset value and writes it to the
writetarget write
buffer as a
writeoffset . An example of copying the contents of two vertex array buffers is shown below:
float vertexData[] = { ... }; glBindBuffer(GL_COPY_READ_BUFFER, vbo1); glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2); glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));
We could also do this by
associating the writetarget buffer with one of the new types of target buffers:
float vertexData[] = { ... }; glBindBuffer(GL_ARRAY_BUFFER, vbo1); glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2); glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));
Having some additional knowledge of how to manipulate buffers, we can already use them in more interesting ways. As you dive into OpenGL, these methods for working with buffers will become more useful. In the next lesson, where we discuss
Uniform Buffer Objects , we still need the
glBufferSubData function.