Transform feedback is a relatively new feature in OpenGL. When OpenGL was first created it was only possible to render to a built-in frame buffer. When OGL2 came out, we got framebuffer objects (through extensions) which allow us to render to a texture. With OGL3 we get transform feedback (also through extensions), which allows us to render vertices to a vertex buffer object. Combined with other OGL3 features like geometry shaders and instancing, we can offload a huge amount of work from the CPU to the GPU. This tutorial won't be implementing anything really useful. Rather, it's meant to quickly explain how to set up and use transform feedback.
In short, transform feedback allows you to capture the raw output of the geometry shader (or vertex shader if there is no geometry shader). Note that fragments are still generated and the fragment shader is still used. That means that it's possible to for example both render to a transform feedback VBO while at the same time rendering to multiple FBOs. This might be useful in some cases, but you usually just want to do render to one or the other. Therefore there is a new GL state called GL_RASTERIZER_DISCARD. By enabling GL_RASTERIZER_DISCARD, the OGL pipeline is terminated after the geometry shader and no fragments are processed. This allows you to do much more generic computing since you can just render from a VBO and output directly to another VBO. The output VBO can then later be used for something else.
What you need to get started with transform feedback is:
- At least a vertex shader and maybe a geometry shader.
- A feedback object (similar to a texture object or a framebuffer object).
- A VBO to render to.
In this example I will create a minimal transform feedback program which "renders" a number of points through transform feedback, then draws the processed points on the screen. This specific program culls points that are near the edge of the screen:

The basic structure of the program is as following:
- Process the points through transform feedback. The geometry shader only outputs the point again if its x and y coordinates are between -0.5 and 0.5.
- The processed points are then rendered to the screen.
It's obviously a pretty useless program. A similar much more realistic scenario would be to do the frustum culling of thousands of 3D models using transform feedback. You'd draw "points" representing each object, check if that point (with a radius) passes the frustum test in the geometry shader and only output the it if it does. You can then use instancing to draw the 3D model X times, where X is the number of points passed that passed the test, using the points that passed to position each instance.
First we'll take a look at the shaders. The vertex shader simply passes through the position of the point to the geometry shader:
1 2 3 4 5 6 7 8 9
| #version 330
layout(location = 0) in vec2 position;
out vec2 tPosition;
void main(){ tPosition = position; } |
The geometry shader only outputs the point if it passes the if-statement:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #version 330
layout(points) in; layout(points, max_vertices = 1) out;
in vec2[] tPosition;
out vec2 outPosition;
void main() { vec2 pos = tPosition[0]; if(pos.x > -0.5 && pos.x < 0.5 && pos.y > -0.5 && pos.y < 0.5){ outPosition = pos; EmitVertex(); EndPrimitive(); } } |
Finally we have a few Java code highlights...
Loading the shader. Notice the (unmissable) call to glTransformFeedbackVaryings(); before linking.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| private void initShader() { shaderProgram = glCreateProgram(); int vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, loadFileSource("shaders/tf/test.vert")); glCompileShader(vertexShader); glAttachShader(shaderProgram, vertexShader); int geometryShader = glCreateShader(GL_GEOMETRY_SHADER); glShaderSource(geometryShader, loadFileSource("shaders/tf/test.geom")); glCompileShader(geometryShader); glAttachShader(shaderProgram, geometryShader); glTransformFeedbackVaryings(shaderProgram, new CharSequence[]{"outPosition"}, GL_INTERLEAVED_ATTRIBS); glLinkProgram(shaderProgram); String log = glGetProgramInfoLog(shaderProgram, 65536); if(log.length() != 0){ System.out.println("Program link log:\n" + log); } positionLocation = glGetAttribLocation(shaderProgram, "position"); } |
Initializing transform feedback.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| private void initTransformFeedback() {
inputData = BufferUtils.createFloatBuffer(NUM_POINTS * 2); inputVBO = glGenBuffers(); outputVBO = glGenBuffers(); glBindBuffer(GL_ARRAY_BUFFER, outputVBO); glBufferData(GL_ARRAY_BUFFER, NUM_POINTS * 2 * 4, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); feedbackObject = glGenTransformFeedbacks(); glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, feedbackObject); glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, outputVBO); glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0); queryObject = glGenQueries(); } |
Point processing with transform feedback:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| private void processPoints() { glEnable(GL_RASTERIZER_DISCARD); glUseProgram(shaderProgram); glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, feedbackObject); glBeginTransformFeedback(GL_POINTS); { glBindBuffer(GL_ARRAY_BUFFER, inputVBO); glBufferData(GL_ARRAY_BUFFER, inputData, GL_STREAM_DRAW); glEnableVertexAttribArray(positionLocation); glVertexAttribPointer(positionLocation, 2, GL_FLOAT, false, 0, 0);
glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, queryObject); glDrawArrays(GL_POINTS, 0, NUM_POINTS); glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN); System.out.println("Points drawn: " + glGetQueryObjecti(queryObject, GL_QUERY_RESULT));
glDisableVertexAttribArray(positionLocation); glBindBuffer(GL_ARRAY_BUFFER, 0); } glEndTransformFeedback();
glUseProgram(0);
glDisable(GL_RASTERIZER_DISCARD); } |
And finally rendering the output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| private void renderOutput() { glBindBuffer(GL_ARRAY_BUFFER, outputVBO); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(2, GL_FLOAT, 0, 0);
glDrawTransformFeedback(GL_POINTS, feedbackObject); glDisableClientState(GL_VERTEX_ARRAY); glBindBuffer(GL_ARRAY_BUFFER, 0); } |
And finally the full Java source code:
http://www.java-gaming.org/?action=pastebin&id=309I'll leave it as a reader's exercise to create a particle engine with transform feedback. Hint: You'll need to use two transform feedback objects and output VBOs and pingpong between them to process the particles.