The Ping-Pong technique
Introduction
In this post I’m going to discuss a common technique in GPGPU projects, this technique allows a shader to use two textures as input and output data so to be able to perform a computation based on more iterations.
The technique described in this post is based on Textures and the Frame Buffer Object, so I suggest you to read my two previous posts before to read this one.
The technique
The base concept of this technique is quite simple, all we need is a Frame Buffer Object and two textures linked to it.
The Ping-Pong technique is used for all the programs that need to store the result of a computation, so all the programs based on more iterations.
The first thing to do is to create a FBO and two textures (with same dimensions and internalFormat), then attach the two texturs to the FBO, after that it’s possible to use one texture as input, so read its values in the shader (vertex and fragment shaders can read one or more textures as a rule) and use the other one as output thanks to the glDrawBuffer function that sets the second texture as output buffer (so the fragments computed in the fragment shader will be wrote in the texture).
After the first iteration, it’s enough to swap the two textures so to use the computed values in the previous step as input for the next computation.
Some implementation details
The key element of this technique is the texture swapping, an easy way to do that is storing the Texture ids in a two elements array and access the array with two integer variables, for example read_tex and write_tex .
So to swap the textures all you have to do is swapping the values of these two variables.
Example code
This is the code used to init properly the FBO and the two textures:
int read_tex = 0;
int write_tex = 1;
GLuint tex[2];
glGenTextures(2, tex);
…
// create and init the FBO
GLuint fb;
glGenFramebuffersEXT(1, &fb);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
…
// create and init the the two textures - this is the code for the first one
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex[read_tex]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, W, H, 0, GL_RGBA, GL_FLOAT, data);
…
// attach the textures to the FBO
GLenum att_point[] = {GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT};
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, att_point[read_tex], GL_TEXTURE_2D, tex[read_tex], 0);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, att_point[write_tex], GL_TEXTURE_2D, tex[write_tex], 0);
…
// create, init and enable the shaders
now everything is ready for the computational loop:
for(int i = 0; i < num_it; i++)
{
// set the second texture as output buffer for the shader
glDrawBuffer(att_point[write_tex]);
// attach the input texture to the first Texture Unit
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex[read_tex]);
glUniform1i(tex0_loc, 0);
// draw a square with the texture on it so to perform the computation
ShaderDraw();
// swap read and write indexes
if(read_tex)
{
read_tex = 0;
write_tex = 1;
}
else
{
read_tex = 1;
write_tex = 0;
}
}