Please send suggestions or corrections for this page to the author at sbuss@ucsd.edu.
This page describes the use of Transform Feedback in OpenGL as used in the Chap1TransformFeedback program. This is part of a series of programs illustrating features of Modern OpenGL. On this page, we only discuss aspects of the software that deal with the use of Transform Feedback, specifically how to calculate data with a shader program and access from the C++ program. For another use of Transform Feedback, see the program ParticlesTransformFeedback.
The code fragments below are taken from the C++ program Chap1TransformFeedback.cpp and the GLSL Shader code in XformFeedbackChap1.glsl. It is recommended to refer to the source code as you read this web page to see how the code fits together. We cannot cover all features of commands here, and it strongly recommended to search to find on-line documentation for the OpenGL commands and keywords to learn more about their functionality.
Chap1TransformFeedback calculates data values (view coordinates of points) that are written to a transform feedback buffer, and then retrieved by the main C++ program. These values are used to build figures in the Chapter 1 of the above-mentioned book, but this aspect of the program is not covered here. Instead, we concentrate on how transform feedback can be used to transfer data from the GPU to the CPU. The transform feedback data is generated on a per-triangle basis.
We allocate a Transform Feedback Buffer object (an FBO) with the commands
unsigned int myTBO; // Transform Buffer Object (TBO)
…
glGenBuffers(1, &myTBO); // Generate an OpenGL buffer
…
int maxNumEdgesFeedback = 3 * (unitSphere.GetNumTriangles() + unitSphere.GetNumTrianglesInSlice());
int sizeOfEdgeFeedback = 3 * sizeof(int) + 4 * sizeof(float);
int maxSizeOfFeedback = sizeOfEdgeFeedback* maxNumEdgesFeedback;
glBindBuffer(GL_ARRAY_BUFFER, myTBO);
glBufferData(GL_ARRAY_BUFFER, maxSizeOfFeedback, (void*)0, GL_STATIC_READ);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, myTBO);
The call to glGenBuffers
allocates a name for a buffer:
this is the same kind of buffer as is used for VBO's
and EBO's, but this buffer will be used for hold
the output from transform feedback.
The call to glBufferData
allocates memory for the buffer.
The value maxSizeOfFeedback
is amount of space to allocate,
measured in bytes. The transform feedback will output three records per
triangle, using a geometry shader, while rendering in
Gl_TRIANGLES
mode. Each record will hold information
about one of the three edges of the triangle; therefore, the total number
records output to the transform feedback buffer will be three (3) times the
number of triangles. Each record will hold three integer values (int
's)
and four floating point values (float
's).
The call to glBindBufferBase
binds the FBO (myTBO
) to
the current Transform Feedback Object. Chap1TransformFeedback
uses the default Transform Feedback Object, so there is no need for us to
call glGenTransformFeedbacks
. (See
ParticlesTransformFeedback for
an example of how to use multiple Transform Feedback Objects.
The transform feedback data will consist of seven values per vertex output by
the Geometry Shader: (three integers and four floating point numbers, giving
information about a single edge in the triangle).
for the position, two for the velocity, and one for the vertex ID). This information
must generally be specified as the shader program is being compiled and linked.
For this the following command is used after the shader programs are compiled
but before they are linked. The seven strings in the array feedbackValues
are the variable names in the shader program that are to output by the transform feedback.
The order of the variables specifies the order of the data as output. The
parameter GL_INTERLEAVED_ATTRIBS
indicates the values will all be
written to the same buffer ("interleaved" mode) instead of to different
output buffers.
// The order of variables in this array is the order they will be put in the transform buffer object (TBO)
const char* feedbackValues[7] = { "drawMode", "vertexNumber1", "vertexNumber2",
"xCoord1", "yCoord1", "xCoord2", "yCoord2" };
glTransformFeedbackVaryings(shaderProgram2, 7, feedbackValues, GL_INTERLEAVED_ATTRIBS);
in Chap1TransformFeedback, transform feedback data is output for every vertex in every triangle.
Since there is a geometry shader, the transform feedback data is output
by the geometry shader, with each call to EmitVertex
outputting
one complete record of transform feedback data. In Chap1TransformFeedback,
the geometry shader
takes triangles as inputs, and outputs points. (See
ParticlesTransformFeedback
for an example of how a vertex shader can output transform feedback data.)
// Feedback transform outputs
out int drawMode;
out int vertexNumber1;
out int vertexNumber2;
out float xCoord1;
out float yCoord1;
out float xCoord2;
out float yCoord2;
The geometry shader must set these values explicitly for each vertex before
calling EmitVertex
.
The code below shows the code in the C++ program that renders (a portion of) the scene while generating Transform Feedback data.
The calls to glBeginTransformFeedback
and
glEndTransformFeedback
start and stop the
shader program's outputting of transform feedback data.
(It is also possible to "pause" the outputting of transform
feedback data.)
The call to glEnable(GL_RASTERIZER_DISCARD);
is used because
the shader program has no fragment shader; hence it does not render anything,
and thus rasterization is not needed. (Rasterization refers to the process of
mapping primitives to particular pixels.)
The variable myTBOquery
holds the name of an OpenGL query object,
as generated by the call to glGenQueries
. The glBeginQuery
and glEndQuery
delineate the beginning and ending of the query.
The call to glGetQueryObjectuiv
retrieves the the results of the query;
in this case, it is the number of records written during Feedback Transform.
unsigned int myTBOquery; // Will hold an OpenGL query object
…
glGenQueries(1, &myTBOquery);
…
glEnable(GL_RASTERIZER_DISCARD); // There is no fragment shader; hence no rasterization should be done.
glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, myTBOquery);
glBeginTransformFeedback(GL_POINTS);
…
unitSphere.RenderSlice(iSlice);
…
unitSphere.Render();
glEndTransformFeedback(); // End of transform feedback
glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
unsigned int numEdgesFedBack;
glGetQueryObjectuiv(myTBOquery, GL_QUERY_RESULT, &numEdgesFedBack);
assert(numEdgesFedBack <= (unsigned int)maxNumEdgesFeedback);
glDisable(GL_RASTERIZER_DISCARD); // Turn rasterization back on
The following code reads the data from the Transform Buffer Object (FBO)
back to the array feedbackFloat
on the CPU. The for
loop
prints out the data to verify its correctness for debugging purposes. Note how typecasting
is used to extract integers versus floats.
(The code in the for
loop uses GLint and GLfloat to avoid errors on systems
with different word sizes.)
// Fetch results from GPU back to CPU and print results
float* feedbackFloat = new float[numEdgesFedBack * 7];
glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeOfEdgeFeedback*numEdgesFedBack, feedbackFloat);
printf("%ld transform feedback records.\n", numEdgesFedBack);
// The following loop print the entire feedback buffer contents
for (int i = 0; i < (int)numEdgesFedBack; i++) {
GLfloat* fd_f = feedbackFloat + i * 7;
GLint* fd_i = (GLint*)fd_f;
printf("%3ld:%3ld, %4ld, %4ld, %f, %f, %f, %f.\n", i, *fd_i, *(fd_i + 1), *(fd_i + 2), *(fd_f + 3), *(fd_f + 4), *(fd_f + 5), *(fd_f + 6));
}