openGL SubTexturing

For those stuck with OpenGL ES 1.1/2.0 in 2018 and later, I did some tests with different methods how to update part of texture from image data (image is of same size as texture).

Method 1: Copy whole image with glTexImage2D:

glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, mWidth, mHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_Pixels );

Method 2: Copy whole image with glTexSubImage2D:

glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, m_Pixels );

Method 3: Copy image part, line by line in a loop:

auto *ptr = m_Pixels + (x + y * mWidth) * 4;
for( int i = 0; i < h; i++, ptr += mWidth * 4 ) {
    glTexSubImage2D( GL_TEXTURE_2D, 0, x, y+i, w, 1, GL_RGBA, GL_UNSIGNED_BYTE, ptr );
}

Method 4: Copy whole width of the image, but vertically copy only part which has changed:

auto *ptr = m_Pixels + (y * mWidth) * 4;
glTexSubImage2D( GL_TEXTURE_2D, 0, 0, y, mWidth, h, GL_RGBA, GL_UNSIGNED_BYTE, ptr );

And here are the results of test done on PC, by 100000 times updating different parts of the texture which were about 1/5th of size of the whole texture.

  • Method 1 - 38.17 sec
  • Method 2 - 26.09 sec
  • Method 3 - 54.83 sec - slowest
  • Method 4 - 5.93 sec - winner

Not surprisingly, method 4 is fastest, as it copies only part of the image, and does it with a single call to glTex...() function.


Edit: Use glPixelStorei. You use it to set GL_UNPACK_ROW_LENGTH to the width (in pixels) of the entire image. Then you call glTexImage2D (or whatever), passing it a pointer to the first pixel of the subimage and the width and height of the subimage.

Don't forget to restore GL_UNPACK_ROW_LENGTH to 0 when you're finished with it.

Ie:

glPixelStorei( GL_UNPACK_ROW_LENGTH, img_width );
char *subimg = (char*)m_data + (sub_x + sub_y*img_width)*4;
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, sub_width, sub_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, subimg );
glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 );

Or, if you're allergic to pointer maths:

glPixelStorei( GL_UNPACK_ROW_LENGTH, img_width );
glPixelStorei( GL_UNPACK_SKIP_PIXELS, sub_x );
glPixelStorei( GL_UNPACK_SKIP_ROWS, sub_y );

glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, sub_width, sub_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_data );

glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 );
glPixelStorei( GL_UNPACK_SKIP_PIXELS, 0 );
glPixelStorei( GL_UNPACK_SKIP_ROWS, 0 );

Edit2: For the sake of completeness, I should point out that if you're using OpenGL-ES then you don't get GL_UNPACK_ROW_LENGTH. In which case, you could either (a) extract the subimage into a new buffer yourself, or (b)...

glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, sub_width, sub_height, 0, GL_RGBA, GL_UNSIGNED_BYTES, NULL );

for( int y = 0; y < sub_height; y++ )
{
    char *row = m_data + ((y + sub_y)*img_width + sub_x) * 4;
    glTexSubImage2D( GL_TEXTURE_2D, 0, 0, y, sub_width, 1, GL_RGBA, GL_UNSIGNED_BYTE, row );
}

Tags:

C++

Opengl