In OpenGL, it is possible to save the results of the vertex or geometry shader into a buffer object. This is a feature known as transform feedback.
When transform feedback is used, a specified set of attributes output
from the vertex shader or geometry shader are written into a buffer.
When no geometry shader is present (remember, geometry shaders are
optional), the data comes from the vertex shader. When a geometry shader
is present, the vertices generated by the geometry shader are recorded.
The buffers used for capturing the output of vertex and geometry
shaders are known as transform feedback buffers. Once data has been placed into a buffer using transform feedback, it can be read back using a function like glGetBufferSubData or by mapping it into the application’s address space using glMapBuffer and reading from it directly. It can also be used as the source of data for subsequent drawing commands.
Transform Feedback
Transform
feedback is a special mode of OpenGL that allows the results of a
vertex or geometry shader to be saved into a buffer. Once the
information is present in the buffer, it can be used as a source of
vertex data for more drawing commands. Any attribute output from the
vertex or geometry shader can be stored into the buffers. However, you
can’t simultaneously record the output of the vertex shader and the
geometry shader. If a geometry shader is active, only the output of the
geometry shader is accessible. If you need the raw data from the vertex
shader, you need to pass it through the geometry shader unmodified. The
position of transform feedback is illustrated in Figure 1.
As you can see,
transform feedback buffers sit between the output of the geometry
shading and vertex assembly stages. As the geometry shader is an
optional stage, if it is not present, the data actually comes from the
vertex shader—this is denoted by dotted lines. Although the diagram
shows transform feedback buffers feeding the vertex assembly stage, this
is only to illustrate the feedback loop that is created (hence the
term, transform feedback).
While OpenGL will allow you to bind the same buffer as a transform
feedback buffer and as a vertex buffer simultaneously, the results will
not be defined if you do this, and you almost certainly won’t get what
you wanted.
The set of vertex attributes, or varyings, to be recorded during transform feedback mode is specified using
void glTransformFeedbackVaryings(GLuint program, GLsizei count, const GLchar ** varyings, GLenum bufferMode);
The first parameter to glTransformFeedbackVaryings
is the name of a program object. The transform feedback varying state
is maintained per program object. This means that different programs can
record different sets of vertex attributes, even if the same vertex or
geometry shaders are used in them. The second parameter is the number of
varyings to record and is also the length of the array whose address is
given in the third parameter. This third parameter is simply an array
of C-style strings giving the names of the varyings to record. These are
the names of the out variables in the
vertex or geometry shader. Finally, the last parameter specifies the
mode in which the varyings are to be recorded. This must be either GL_SEPARATE_ATTRIBS or GL_INTERLEAVED_ATTRIBS. If bufferMode is GL_INTERLEAVED_ATTRIBS, the varyings are recorded into a single buffer, one after another. If bufferMode is GL_SEPARATE_ATTRIBS, each of the varyings is recorded into its own buffer.
Consider the following piece of vertex shader code, which declares the output varyings:
out vec4 vs_position_out;
out vec4 vs_color_out;
out vec3 vs_normal_out;
out vec3 vs_binormal_out;
out vec3 vs_tangent_out;
To specify that the varyings vs_position_out, vs_color_out,
and so on should be written into a single interleaved transform
feedback buffer, the following C code could be used in your application:
static const char * varying_names[] =
{
"vs_position_out",
"vs_color_out",
"vs_normal_out",
"vs_binormal_out",
"vs_tangent_out"
};
glTransformFeedbackVaryings(program, 5, varying_names,
GL_INTERLEAVED_ATTRIBS);
Not all of the outputs from
your vertex (or geometry) shader need to be stored into the transform
feedback buffer. It is possible to save a subset of the vertex shader
outputs to the transform feedback buffer and send more to the fragment
shader for interpolation. Likewise, it is also possible to save some
outputs from the vertex shader into a transform feedback buffer that are
not used by the fragment shader. Because of this, outputs from the
vertex shader that may have been considered inactive (because they’re
not used by the fragment shader) may become active due to their being
stored in a transform feedback buffer. Therefore, after specifying a new
set of transform feedback varyings by calling glTransformFeedbackVaryings, it is necessary to link the program object using
Once the transform
feedback varyings have been specified and the program has been linked,
it may be used as normal. Before actually capturing anything, you need
to bind a buffer object as the transform feedback buffer. When you have
specified the transform feedback mode as GL_INTERLEAVED_ATTRIBS, all of the stored vertex attributes are written one after another into a single buffer. To specify this buffer, call
glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, buffer);
Here, GL_TRANSFORM_FEEDBACK_BUFFER tells OpenGL that we want to bind a buffer to be used to store the results of the vertex or geometry shader to the GL_TRANSFORM_FEEDBACK_BUFFER binding point. The second parameter is the name of the buffer object that we previously created with a call to glGenBuffers.
Before any data can be
written to a buffer, space must be allocated in the buffer for it. To
allocate space without specifying data, call
glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, size, NULL, GL_DYNAMIC_COPY);
The first parameter is the
buffer to allocate space for. You can use any buffer binding you like
just for the purpose of binding a buffer and allocating space for it.
However, OpenGL might make assumptions about what the buffer is going to
be used for based on the first binding point it is bound to, and so,
especially if this is a new buffer, the GL_TRANSFORM_FEEDBACK_BUFFER binding point is a good choice. The size
parameter specifies how much space you want to allocate in bytes. This
is up to your application’s needs, but if, during transform feedback,
too much data is generated to fit into the buffer, the excess will be
thrown away. NULL tells OpenGL that no data is being given that you only want to allocate space for later. The last parameter, usage, gives OpenGL a hint as to what you plan to do with the buffer.
There are many possible values for usage, but GL_DYNAMIC_COPY is probably a good choice for a transform feedback buffer. The DYNAMIC part tells OpenGL that the data is likely to change often but will likely be used a few times between each update. The COPY
part says that you plan to update the data in the buffer through OpenGL
functionality (such as transform feedback) and then hand that data back
to OpenGL for use in another operation (such as drawing).
To specify which buffer
the transform feedback data will be written to, you need to bind a
buffer to one of the indexed transform feedback binding points. There
are actually multiple GL_TRANSFORM_FEEDBACK_BUFFER binding points for this purpose, which are conceptually separate, but related to the general binding GL_TRANSFORM_FEEDBACK_BUFFER binding point. A schematic of this is shown in Figure 2.
To bind a buffer to any of the indexed binding points, call
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, index, buffer);
As before, GL_TRANSFORM_FEEDBACK_BUFFER tells OpenGL that we’re binding a buffer object to store the results of transform feedback, and the last parameter, buffer, is the name of the buffer object we want to bind. The extra parameter, index, is the index of the GL_TRANSFORM_FEEDBACK_BUFFER
binding point. An important thing to note is that there is no way to
directly address any of the extra binding points provided by glBindBufferBase through functions like glBufferData or glCopyBuffer. However, when you call glBindBufferBase, it actually binds the buffer to the indexed binding point and
to the generic binding point. Therefore, you can use the extra binding
points to allocate space in the buffer if you access the general binding
point right after calling glBindBufferBase.
A slightly more advanced version of glBindBufferBase is glBindBufferRange, whose prototype is
void glBindBufferRange(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size);
The glBindBufferRange function allows you to bind a section of a buffer to an indexed binding point, whereas glBindBuffer and glBindBufferBase can only bind the whole buffer at once. The first three parameters (target, index, and buffer) have the same meanings as in glBindBufferBase. The offset and size
parameters are used to specify the start and length of the section of
the buffer that you’d like to bind, respectively. You can bind different
sections of the same buffer to several different indexed binding points
simultaneously. This enables you to use transform feedback in GL_SEPARATE_ATTRIBS
mode to write each attribute of the output vertices into separate
sections of a single buffer. If your application packs all attributes
into a single vertex buffer and uses glVertexAttribPointer
to specify nonzero offsets into the buffer, this allows you to make the
output of transform feedback match the input of your vertex shader.
If you specified that all of the attributes should be recorded into a single transform feedback buffer by using the GL_INTERLEAVED_ATTRIBS parameter to glTransformFeedbackVaryings, the data will be written into the buffer bound to the first GL_TRANSFORM_FEEDBACK_BUFFER binding point (that with index zero). However, if you specified that the mode for transform feedback is GL_SEPARATE_ATTRIBS, each output from the vertex shader will be recorded into its own separate buffer (or section of a buffer, if you used glBindBufferRange). In this case, you need to bind multiple buffers or buffer sections as transform feedback buffers. The index
parameter must be between zero and one less than the maximum number of
varyings that can be recorded into separate buffers using transform
feedback mode. This limit depends on your graphics hardware and drivers
and can be found by calling glGetIntegerv with the GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS parameter. This limit is also applied to the count parameter to glTransformFeedbackVaryings.
There is no upper limit on the number of separate varyings that can be written to transform feedback buffers in GL_INTERLEAVED_ATTRIBS mode, but there is a maximum number of components that can be written into a buffer. For example, it is possible to write more vec3s than vec4s into a buffer using transform feedback. Again, this limit depends on your graphics hardware and can be found using glGetIntegerv with the GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS parameter.
It is not possible to write
one set of output varyings interleaved into one buffer while writing
another set of attributes into another buffer. When transform feedback
is active, the output varyings are either all stored, interleaved into
one buffer, or stored packed into several different buffers or sections
of buffers. Therefore, if you plan to use transform feedback to generate
vertex data for subsequent passes, you need to consider this when you
plan your input vertex layout. The vertex shader is generally a little
more flexible in the way that it is able to read vertex data than in the
way data can be written through transform feedback.
Once the buffers that are to
receive the results of the transform feedback have been bound, transform
feedback mode is activated by calling
void glBeginTransformFeedback(GLenum primitiveMode);
Now whenever vertices pass
through a vertex or geometry shader, output varyings from the later
shader will be written to the transform feedback buffers. The parameter
to the function, primitiveMode, tells OpenGL what types of geometry to expect. The acceptable parameters are GL_POINTS, GL_LINES, or GL_TRIANGLES. When you call glDrawArrays or another
OpenGL drawing function, the basic geometric type must match what you
have specified as the transform feedback primitive mode, or you must
have a geometry shader that outputs the appropriate primitive type. For
example, if primitiveMode is GL_TRIANGLES, you must call glDrawArrays with GL_TRIANGLES, GL_TRIANGLE_STRIP, or GL_TRIANGLE_FAN, or you must have a geometry shader that produces GL_TRIANGLE_STRIP primitives. The mapping of transform feedback primitive mode to draw types is shown in Table 1.
Table 1. Values for primitiveMode
Value of PrimitiveMode | Allowed Draw Types |
---|
GL_POINTS | GL_POINTS |
GL_LINES | GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP |
GL_TRIANGLES | GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN |
Vertices are recorded
into the transform feedback buffers until transform feedback mode is
exited or until the space allocated for the transform feedback buffers
is exhausted. To exit transform feedback mode, call
glEndTransformFeedback();
All rendering that occurs between a call to glBeginTransformFeedback and glEndTransformFeedback results in data being written into the currently bound transform feedback buffers. Each time glBeginTransformFeedback
is called, OpenGL starts writing data at the beginning of the buffers
bound for transform feedback, overwriting what might be there already.
Some care should be taken while transform feedback is active as changing
transform feedback state between calls to glBeginTransformFeedback and glEndTransformFeedback
is not allowed. For example, it’s not possible to change the transform
feedback buffer bindings or to resize or reallocate any of the transform
feedback buffers while transform feedback mode is active.