Buffer Object: Difference between revisions
No edit summary
No edit summary
|Line 28:||Line 28:|
The <code>usage</code> parameter can be very confusing.
The <code>usage</code> parameter can be very confusing.
Revision as of 08:50, 15 August 2009
Buffer Objects is the general term for unformatted linear memory allocated by the OpenGL context. These can be used to store vertex data, pixel data retrieved from images or the framebuffer, and a variety of other things.
Buffer objects were initially introduced in the ARB_vertex_buffer_object extension. They have been part of core OpenGL since version 1.5. This discussion will pertain specifically to the core version of buffer objects, though it is essentially identical to the extension version.
While buffer objects themselves are relatively old, some of the uses of them are much more recent.
Buffer Objects are OpenGL Objects; they therefore follow all of the rules of regular OpenGL objects. To create a buffer object, you call
glGenBuffers. Deleting them uses
glDeleteBuffers. These use the standard Gen/Delete paradigm as most OpenGL objects.
As with the standard OpenGL object paradighm, this only creates the object's name, the reference to the object. To actually create the object itself, you must bind it to the context. You do this using the following API:
void glBindBuffer(enum target, uint bufferName)
target defines how you intend to use this binding of the buffer object. When you're just creating and/or filling the buffer object with data, the target you use doesn't matter much. It matters more when you intend to tell OpenGL to use the data in the buffer in some way.
To create the actual memory storage for a buffer object, you use this API:
void glBufferData(enum target, sizeiptr size, const void *data, enum usage)
target parameter is just like the one for
glBindBuffer; it says which bound buffer to modify.
size represents how many bytes you want to allocate in this buffer object.
While it technically does not matter how you bind the buffer for creations vs. how you use it (create it with one target, unbind and bind it for use as another target) this is not recommended if you can at all help it. OpenGL implementations can, and will, make assumptions based on what target you used to create a buffer object, so don't try to confuse them.
data parameter is a pointer to user memory that will be copied into the buffer object's data store. If this value is NULL, then no copying will occur, and the buffer object's data will be undefined.
usage parameter can be very confusing.
Buffer Object Usage
Buffer objects are general purpose memory storage blocks allocated by OpenGL. They are intended to be used in a great many ways. To give the implementation great flexibility in exactly what a particular buffer object's data store will be, so as to better optimize performance, the user is required to give usage hints. These provide a general description as to how exactly the user will be using the buffer object.
There are two independent parts to the usage pattern: how the user will be reading/writing from/to the buffer, and how often the user will be changing it relative to the use of the data.
There are three ways that the user can specify the data.
- DRAW: The user will be uploading the data, but the user will not read it back.
- READ: The user will not be uploading the data (it will be filled in by GL commands), but the user will be reading it back.
- COPY: The user will be neither uploading nor reading the data.
DRAW is useful for, as the name suggests, drawing. Buffer objects holding vertex data are specified as DRAW.
READ is used when a buffer object is an intermediate for asynchronous delivery of image data. You can use
glGetTexSubImage to read data into a buffer object.
COPY is used when a buffer object is used to pass data from one place in OpenGL to another. For example, you can read image data into a buffer, then use that image data as vertex data in a draw call. You can also use transform feedback to achieve the same thing in a more direct way. You have the feedback data go to a buffer object, then use that buffer object as vertex data.
There are three ways that the user can be changing the data.
- STATIC: The user will set the data once.
- DYNAMIC: The user will set the data occassionally.
- STREAM: The user will be changing the data after every use. Or almost every use.
STREAM is pretty easy to understand: the buffer object's contents will be updated after almost every use. STATIC is pretty easy to understand too. The buffer object's contents will be updated once and never changed.
What is unclear is when DYNAMIC becomes STREAM or STATIC. These are only hints, after all. It is pefectly legal OpenGL code to modify a STATIC buffer after it has been created, or to never modify a STREAM buffer.
Is it better to use STATIC for buffers that are updated very infrequently? Is it better to use DYNAMIC for buffers that get updated frequently, but not at STREAM speed? Is it better to use DYNAMIC for buffers that get partially updated? These are questions that can only be answered with careful profiling. And even then, the answer will only be accurate for that particular driver version of that particular hardware.
In any case, STREAM, STATIC, and DYNAMIC can be matched with READ, DRAW, and COPY in any combination. STREAM_COPY means that you will be doing transform feedback writes (or other kinds of GL-based writes) into the buffer after almost every use; it will not be updated with
BufferSubData or similar functions. STATIC_READ means that you will fill the buffer up from the GL, but you will only do this once.
We have seen that
BufferData can be used to update the data in a buffer object. However, this also recreates the buffer object, causing it to be reallocated. This is not usually what one wants, as recreating the buffer can often be a heavyweight operation.
Instead, one can use the following API:
void glBufferSubData(enum target, intptr offset, sizeiptr size, const void *data)
offset parameter is an integer offset into the buffer object where we should begin updating. The
size parameter is the number of bytes we should copy out of
data. For obvious reasons,
data cannot be NULL.
Buffer objects provide a number of possible usage patterns for streaming. Exactly which will work best depends on the particulars of the hardware.
Tf you're streaming data, STREAM is going to need to be in your usage. And since we're talking about updating from the user side, you should be using STREAM_DRAW.
There is a parallelism problem that can occur when streaming data. The OpenGL specification permits an implementation to delay the execution of drawing commands. This allows you to draw a lot of stuff, and then let OpenGL handle things on its own time. Because of this, it is entirely possible that, well after you called the rendering function with a buffer object, you might start trying to stream vertex data into that buffer. If this happens, the OpenGL specification requires that the thread halt until all drawing commands that could be affected by your update of the buffer object complete. This obviously misses the whole point of streaming.
This is going to be your main source of woe.
There is one tried-and-true method of avoiding this: manual double-buffering. That is, allocate two buffer objects of the same size. Fill one up and render with it, then switch to the other one when you need to stream some new vertices in.
This is nice, and it gets around the above issue. But it has problems. Namely, that it takes up 2x the memory. Also, the STREAM hint is designed to deal with precisely this issue, so it is entirely possible that the implementation may double-buffer for you.
Instead, you can try a variety of techniques to force the implementation to do what you need.
glMapBufferRange with the GL_MAP_INVALIDATE_BUFFER_BIT set is one way to do it. Invalidating the buffer tells OpenGL that the entire buffer's contents will not be needed. This gives OpenGL the opertunity to orphan the buffer and allocate a new one. It also conveniently maps the buffer, so if you need to map the buffer to upload your data, there you are.
If you call
glBufferData with a NULL data pointer and the same usage hints and size, the OpenGL implementation can take this as a sign that you no longer care about the current contents of the buffer. Again, this allows OpenGL to orphan the buffer and allocate a new one.
Both of these can give the effect of double-buffering.
The deepest of the deep magic comes in
glMapBufferRange with GL_MAP_UNSYNCHRONIZED_BIT. This guarantees that you will never halt due to the buffer being in use. Unfortunately, it also means that you can get a race condition, where you are updating a buffer object while it is being read from. The unsyncrhonized flag will prevent OpenGL from trying to stop this, but it won't prevent OpenGL from rendering wrong stuff when it does happen.
To prevent it on your end, you can use the ARB_sync core extension (core in version 3.2). This allows you to ask whether a particular rendering command has finished by putting a fence after that command. Thus, if it has finished, you can do the streaming. If it hasn't, you can choose to do something else. That way, your thread isn't stopped.
Most of the uses of buffer objects involve binding them to a certain target, then calling a function that behaves differently based on having a buffer object in that target. Usually, these functions take a pointer as one of their parameters. When a buffer object is bound to certain targets, it causes some functions that take a pointer parameter to treat that parameter as an offset into the buffer object. Thus, rather than pulling data from client memory pointers, they pull it from the bound buffer object.
Indeed, in more recent versions of OpenGL, some of these functions are no longer allowed to take a regular pointer at all. They can only be used when a buffer object is bound to the proper target.
Easily, the most common usage for buffer objects is as vertex array data. This was the original use behind them, though other uses were expected and designed into the feature. There is a section on Vertex Buffer Objects and their use. OpenGL 3.1 and above no longer allow the use of vertex arrays without buffer objects.
When a buffer object is bound to the GL_ARRAY_BINDING target, all
glVertexAttribPointer commands, as well as the equivalent fixed function commands (
glTexCoordPointer, etc) will use the pointer value as an absolute offset from the beginning of the buffer object. More details are available on the page on Vertex Buffer Objects.
Similarly, when a buffer object is bound to GL_ELEMENT_ARRAY_BINDING, all functions of the form
glDraw*Elements will use the pointer value as an absolute offset from the beginning of the bound buffer object. The indices will be taken from the buffer object.
In GL 3.1 and above (without compatibility), none of these functions will work in the absence of a buffer object. You simply are not allowed to use client memory for drawing in these versions of OpenGL.
The GL_COPY_READ_BUFFER and GL_COPY_WRITE_BUFFER (core extension GL_ARB_copy_buffer, core in version 3.1 and above) are used for copying buffer objects. The function
glCopyBufferSubData is used to copy data between buffers.
The GL_PIXEL_PACK_BUFFER and GL_PIXEL_UNPACK_BUFFER (extension GL_ARB_pixel_buffer_object, core in versions 2.1 and above) are used to store or read pixel data. If a buffer is bound to GL_PIXEL_PACK_BUFFER,
glDrawPixels are all affected. These functions will read their data from the bound buffer object instead of a client pointer. Similarly, if a buffer is bound to GL_PIXEL_UNPACK_BUFFER,
The pack and unpack bindings are best used for asynchronous transfers of data to/from the GPU. When these functions read/write from/to client memory, they must block for a period of time. By having their source/destinations be buffer objects, which have a very controlled access scheme, it allows these commands to work asynchronously.
The GL_TEXTURE_BUFFER (core extension GL_ARB_texture_buffer_object, core in versions 3.0 and above) binding allows a special 1D texture to have its storage be a buffer object. This binding point is how the buffer gets attached to the texture object.
The GL_TRANSFORM_FEEDBACK_BUFFER (core in versions 3.0 and above) binding is used to implement storing the results of the vertex (and geometry, where available) shader in a buffer object. This binding point is the buffer that gets written into.
The GL_UNIFORM_BUFFER_BINDING (core extension GL_ARB_uniform_buffer_object, core in versions 3.1 and above) binding is used to allow buffer objects to store uniforms for shader programs. This binding point is used to attach buffers containing uniforms to program objects.