Duplicating an OpenGL Texture

 

Suppose if you need to duplicate an OpenGL texture. How you will manage it?

Poor Programmer’s Method

In this way you will be just loading a texture, and duplicate mainly with the help of glGetTexImage and glTexImage APIs. This is method is extremely low performing and will not work with the textures rendered using frame buffer objects. But still the code works fine with the texture you simply load from the file. Here’s the sample snippet for the same.

[sourcecode language='cpp']
GLuint CopyTextureCPU( GLuint nTexID_in, GLsizei width, GLsizei height )
{
// Generate the destination texture
GLuint nTexID_out = 0;
glGenTextures( 1, &nTexID_out );
glBindTexture( GL_TEXTURE_2D, nTexID_out );
glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR );

// bind the source texture and get the texels
unsigned char* buff = new unsigned char[width*height*3 ];
glBindTexture( GL_TEXTURE_2D, nTexID_in);
glGetTexImage( GL_TEXTURE_2D, 0,GL_RGB,GL_UNSIGNED_BYTE, buff );

// bind the output texture and copy the image
glBindTexture( GL_TEXTURE_2D, nTexID_out );
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, width,height, 0, GL_RGB, GL_UNSIGNED_BYTE, buff );
glBindTexture( GL_TEXTURE_2D, 0 );
return nTexID_out;
}
[/sourcecode]

Using Frame buffer Object

This is the best and fast method (in my experience). You are just instructing the GPU to transfer the source texture to the destination. There are two different methods to do this. In first method, you’re creating a frame buffer and attaching the source texture as render texture. Then you will be binding the newly created frame buffer and bind the destination texture for download. With the help of glCopyTexImage, you can download the texture from the attached frame buffer. The issue with this method is, once if you specify the rendering texture created with some size (say 512, 512), you can’t attach a new texture which is having different size that the first one. It’s one of the limitations with Frame buffer object. Before using this code, you’ve to ensure that your hardware supports "GL_EXT_framebuffer_object" extension.

Method 1:

[sourcecode language='cpp']
GLuint CopyTexture1( GLuint nTexID_in, GLsizei width, GLsizei height )
{
GLuint nTexID_out = 0;
GLuint nFbo = 0;
// Create the duplicate texture
glGenTextures( 1, &nTexID_out);
glBindTexture( GL_TEXTURE_2D, nTexID_out );
glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR );
// Allocate memory for texture
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height,
0, GL_RGBA, GL_UNSIGNED_BYTE, 0 );

// Create and bind frame buffer
glGenFramebuffersEXT( 1, &nFbo);
glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, nFbo);
glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_2D, nTexID_in, 0 );

// Copy texture from frame buffer
glCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, width, height );

// Do the cleanup
glBindTexture( GL_TEXTURE_2D, 0 );
glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0);
glDeleteFramebuffersEXT( GL_FRAMEBUFFER_EXT, &nFbo );
return nTexID_out;
}
[/sourcecode]

Alternatively you can bind the destination texture to the frame buffer render texture and bind the source texture for texture mapping. This method is also fast as previous one. There are not big differences except the usage of source and destination textures. But in this method, you should have use texture mapping for copying but in the previous method, you can get it using single OpenGL call (glCopyTexImage).

[sourcecode language='cpp']
GLuint CopyTexture2( GLuint nTexID_in, GLsizei width, GLsizei height )
{
GLuint nTexID_out = 0;
GLuint nFbo = 0;
// Create the duplicate texture
glGenTextures( 1, &nTexID_out );
glBindTexture( GL_TEXTURE_2D, nTexID_out );
glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR );

// Create Frame buffer object and attach destination texture for drawing
glGenFramebuffersEXT( 1, &nFbo);
glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, nFbo);
glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_2D, nTexID_out, 0 );

// Get the current view port to restore back after drawing
GLint viewport[4];
glGetIntegerv( GL_VIEWPORT, viewport );
// Prepare to draw (map) source texture to destination texture
glViewport( 0, 0, width, height );
glBindTexture( GL_TEXTURE_2D , nTexID_in );

// Start drawing
glClear( GL_COLOR_BUFFER_BIT );
glBegin( GL_POLYGON );
glTexCoord2f( 0, 0 ); glVertex2f( -1,-1 );
glTexCoord2f( 0, 1 ); glVertex2f( -1, 1 );
glTexCoord2f( 1, 1 ); glVertex2f( 1, 1 );
glTexCoord2f( 1, 0 ); glVertex2f( 1,-1 );
glEnd();

// unbind texture and frame buffer
glBindTexture( GL_TEXTURE_2D , 0 );
glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
glDeleteFramebuffersEXT( GL_FRAMEBUFFER_EXT, &nFbo );
// Restore the old view port
glViewport( viewport[0],viewport[1],viewport[2],viewport[3]);
return nTexID_out;
}
[/sourcecode]

You can also use pixel buffer objects for copying images. But I feel not to include in this post to keep it as simple and minimal

 
This entry was posted in Uncategorized. Bookmark the permalink.
  • Mohammed Fathy

    Excellent and compact work!

    I just have a comment about CopyTexture2(…). With its current form, texture border colors will affect the process. Technical details of the problem are given after the solution.

    Two solutions are possible to this problem:

    The first is to assign texture coordinates (s, t) in the region [0+.5/w, 1-.5/w] x [0+.5/h, 1-.5/h] instead of [0, 1] x [0, 1]:
    # float ds = 0.5f / width;
    # float dt = 0.5f / height;
    # glBegin( GL_POLYGON );
    # glTexCoord2f( 0 + ds, 0 + dt ); glVertex2f( -1,-1 );
    # glTexCoord2f( 0 + ds, 1 – dt ); glVertex2f( -1, 1 );
    # glTexCoord2f( 1 – ds, 1 – dt ); glVertex2f( 1, 1 );
    # glTexCoord2f( 1 – ds, 0 + dt ); glVertex2f( 1,-1 );
    # glEnd();

    The other simpler one is to change the wrap mode in both s and t directions to GL_CLAMP_TO_EDGE:
    # glTexParameter2i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    # glTexParameter2i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    However, this will produce slightly different results than the above solution.

    The problem is that with GL_LINEAR filters, the texture coordinates used should start a little after 0 and end a little before 1. Otherwise, border texels will affect the coloring of the fragments near the edges. This is because OpenGL translates the texture coordinates (s, t) at a given fragment to texel coordinates (u, v) as follows:

    u = s*w – .5
    v = t*h – .5

    Thus, a fragment with texture coordinates (s, t) = (0, 0) will map to texel (-.5, -.5). With Linear filtering, the returned texel be the average of 3 border texels at (-1, 0), (0, -1), (-1, -1) and the bottom left texel of the texture (0, 0).

  • Frank

    Thanks for the info!

    I tried using the second piece of code and came across a seg fault. This turned out to be because the following line:
    glDeleteFramebuffersEXT( GL_FRAMEBUFFER_EXT, &nFbo );

    Should be:
    glDeleteFramebuffersEXT( 1, &nFbo );

    This is also true of the third code segment.

  • http://www.cheapbootsforsale.us ugg boots

    “Here air jordan 21 products xx, has fashion model, superior quality and service, cheap price and updates quickly.I support strongly always! I want to buy XX, I hesitate to select which style more better.Hope your unique recommends.