OpenGL transform feedback

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:

http://img706.imageshack.us/img706/2012/transformfeedbackscreen.png

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:

#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:

#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.

	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);
		
		//This line tells the shader which output attributes from the geometry shader 
		//we want to save to the transform feedback output VBO. It's an array of varying
		//names followed by an enum controlling how we want the data to be stored.
		//GL_INTERLEAVED_ATTRIBS tells OpenGL to put them in the same VBO ordered in
		//the way specified by the array.
		//It's very important to call this BEFORE linking the program.
		glTransformFeedbackVaryings(shaderProgram, new CharSequence[]{"outPosition"}, GL_INTERLEAVED_ATTRIBS);
		
		//Note that we don't even have a fragment shader for this shader program.
		
		glLinkProgram(shaderProgram);
        String log = glGetProgramInfoLog(shaderProgram, 65536);
        if(log.length() != 0){
            System.out.println("Program link log:\n" + log);
        }
        
        //Save the input variable location
        positionLocation = glGetAttribLocation(shaderProgram, "position");
        
	}

Initializing transform feedback.

	private void initTransformFeedback() {

		//This is the buffer we fill with random points to process.
		inputData = BufferUtils.createFloatBuffer(NUM_POINTS * 2);
		//And the VBO which we upload the data to.
		inputVBO = glGenBuffers();
		
		//This is the data in which the processed points will end up.
		//We make it big enough to fit all input points, in case all
		//of them pass. If the buffer is filled, additional data will
		//simply be discarded.
		outputVBO = glGenBuffers();
		glBindBuffer(GL_ARRAY_BUFFER, outputVBO);
		glBufferData(GL_ARRAY_BUFFER, NUM_POINTS * 2 * 4, GL_STATIC_DRAW);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		
		//We create our transform feedback object. We then bind it and
		//tell it to store its output into outputVBO.
		feedbackObject = glGenTransformFeedbacks();
        glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, feedbackObject);
        glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, outputVBO);
        glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
        
        //We also create a query object. This object will be used to
        //query how many points that were stored in the output VBO.
        queryObject = glGenQueries();
	}

Point processing with transform feedback:

	private void processPoints() {
		
		//Disable pixel rendering, we're doing transform feedback baby!
		glEnable(GL_RASTERIZER_DISCARD);
		
		//Bind the shader...
		glUseProgram(shaderProgram);
		
		//and then the feedback object
		glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, feedbackObject);
		glBeginTransformFeedback(GL_POINTS);
		{
			//Between glBeginTransformFeedback(GL_POINTS) and glEndTransformFeedback()
			//we can of course only draw points.
			
			//Bind and update the input data VBO.
			glBindBuffer(GL_ARRAY_BUFFER, inputVBO);
			glBufferData(GL_ARRAY_BUFFER, inputData, GL_STREAM_DRAW);
			
			//Enable our only shader input attribute.
			glEnableVertexAttribArray(positionLocation);
			glVertexAttribPointer(positionLocation, 2, GL_FLOAT, false, 0, 0);

			//Draw the points with a standard glDrawArrays() call, but wrap it in
			//a query so we can determine exactly how many points that were stored
			//in outputVBO.
			//WARNING: Querying is VERY SLOW and is only done so we can write out
			//how many points that passed to the console! It's possible to draw 
			//all points that passed without a query! See renderOutput()!
			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));

			//Clean up after us...
			glDisableVertexAttribArray(positionLocation);
			glBindBuffer(GL_ARRAY_BUFFER, 0);
			
		}
		glEndTransformFeedback();

		glUseProgram(0);

		glDisable(GL_RASTERIZER_DISCARD);
	}

And finally rendering the output:

	private void renderOutput() {
		
		//Bind the outputVBO just like any other VBO
		glBindBuffer(GL_ARRAY_BUFFER, outputVBO);
		
		//We're using the fixed functionality pipeline here,
		//so just set it up to read positions from the outputVBO.
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(2, GL_FLOAT, 0, 0);

		//glDrawTransformFeedback is a special draw call that is very similar to 
		//glDrawArrays(). It allows us to draw all points that were output by our
		//shader without having to involve the CPU to determine how many they were.
		//This is the same as glDrawArrays(GL_POINTS, 0, num_points_that_passed);,
		//but is a LOT faster than if we had used a query object to get the count
		//and then manually calling glDrawArrays() with that count.
		glDrawTransformFeedback(GL_POINTS, feedbackObject);
		
		//Clean up...
		glDisableClientState(GL_VERTEX_ARRAY);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		
	}

And finally the full Java source code: http://www.java-gaming.org/?action=pastebin&id=309

I’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.

How would you do something like this without a geometry shader?

Looks like I need to start learning how they work :smiley:

Easy, just don’t attach a geometry shader and pass the vertex shader output attributes you want to glTransformFeedbackVaryings() instead. It’s not very useful without a geometry shader, though. The whole point is the ability to output a varying number of primitives since that allows you do stuff like culling (only output visible stuff) and particle updating (only output surviving particles). It can of course also be used to generate geometry that needs to be used more than once. On OGL4 hardware, a common usage is to catch hardware tessellated geometry. This geometry can then be used to render shadow maps etc to ensure that the same objects are tessellated the same from all view angles.