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.

I. Setting up the transform feedback object

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.

II. Compiling the shader for transform feedback

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);

III. Outputting transform feedback values from the shader.

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.

IV. Generating and retrieving transform feedback data.

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));
    }