Corrupt compressed 2D texture array texture on NV

Hello,

I am having trouble successfully using the texture arrays extension. The sample application provided by Nvidia in their OpenGL SDK works fine, but when I modify the example, it fails to work in some cases.

This works:
Uncompressed RGB texture, fully mipmapped (Nvidia sample) or not mipmapped at all.

Using partial mipmap specification (not down to 1x1) on the uncompressed texture makes subsequent rendering with the texture corrupt.

Also, using DXT compressed textures does not work at all, whether they are mipmapped or not.

Has anybody managed to use texture arrays with 2D DXT textures? This seems like the target use-case of the extension so I don’t know what to think… I am currently using a GTX 280.

Here is my test code which creates and transfers the textures. I create 6 different texture configurations. Is anything blatantly wrong in there?

I did not include the rendering loop, which is simply “clear, bind texture, bind shader, render quad” and which works for some texture configurations. If anybody is interested, I’d be happy to provide the whole testapp.

Thanks!

// 3 DXT blocks (red, green and blue)
unsigned char DXTRedBlock[8];
DXTRedBlock[0] = 0;
DXTRedBlock[1] = 248;
DXTRedBlock[2] = 0;
DXTRedBlock[3] = 248;
DXTRedBlock[4] = 0;
DXTRedBlock[5] = 0;
DXTRedBlock[6] = 0;
DXTRedBlock[7] = 0;

unsigned char DXTGreenBlock[8];
DXTGreenBlock[0] = 224;
DXTGreenBlock[1] = 7;
DXTGreenBlock[2] = 224;
DXTGreenBlock[3] = 7;
DXTGreenBlock[4] = 0;
DXTGreenBlock[5] = 0;
DXTGreenBlock[6] = 0;
DXTGreenBlock[7] = 0;

unsigned char DXTBlueBlock[8];
DXTBlueBlock[0] = 31;
DXTBlueBlock[1] = 0;
DXTBlueBlock[2] = 31;
DXTBlueBlock[3] = 0;
DXTBlueBlock[4] = 0;
DXTBlueBlock[5] = 0;
DXTBlueBlock[6] = 0;
DXTBlueBlock[7] = 0;

// The 3 DXT blocks grouped
unsigned char Blocks3[24];
memcpy( Blocks3, DXTRedBlock, 8 );
memcpy( Blocks3+8, DXTGreenBlock, 8 );
memcpy( Blocks3+16, DXTBlueBlock, 8 );

// 3 uncompressed 4x4 textures (Solid red, green and blue)
unsigned char Red4x4[48];
for( int i = 0; i < 48; i++ )
	Red4x4[i] = (i % 3) == 0 ? 255 : 0;

unsigned char Green4x4[48];
for( int i = 0; i < 48; i++ )
	Green4x4[i] = (i % 3) == 1 ? 255 : 0;

unsigned char Blue4x4[48];
for( int i = 0; i < 48; i++ )
	Blue4x4[i] = (i % 3) == 2 ? 255 : 0;

glGenTextures(nNbTextures, texid);

// First texture array (NOT OK)
// -DXT compressed
// -Not mipmapped
// -Level 0 allocated and then subloaded
glBindTexture( GL_TEXTURE_2D_ARRAY_EXT, texid[0]);
glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 0, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 4, 4, 3, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 0, 4, 4, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTRedBlock);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 1, 4, 4, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTGreenBlock);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 2, 4, 4, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTBlueBlock);

// Second texture array (NOT OK)
// -DXT compressed
// -Fully mipmapped (3 levels)
// -Levels allocated and then subloaded
glBindTexture( GL_TEXTURE_2D_ARRAY_EXT, texid[1]);
glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 0, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 4, 4, 3, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 0, 4, 4, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTRedBlock);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 1, 4, 4, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTGreenBlock);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 2, 4, 4, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTBlueBlock);
glTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 2, 2, 3, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 1, 0, 0, 0, 2, 2, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTRedBlock);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 1, 0, 0, 1, 2, 2, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTGreenBlock);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 1, 0, 0, 2, 2, 2, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTBlueBlock);
glTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 2, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 1, 1, 3, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 2, 0, 0, 0, 1, 1, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTRedBlock);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 2, 0, 0, 1, 1, 1, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTGreenBlock);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 2, 0, 0, 2, 1, 1, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTBlueBlock);

// Third texture array (NOT OK)
// -DXT compressed
// -Fully mipmapped (3 levels)
// -Mipmaps allocated and initialized at once
glBindTexture( GL_TEXTURE_2D_ARRAY_EXT, texid[2]);
glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 0, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 4, 4, 3, 0, 24, Blocks3);
glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 2, 2, 3, 0, 24, Blocks3);
glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 2, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 1, 2, 3, 0, 24, Blocks3);

// Fourth texture array (OK)
// -Uncompressed RGB8 data
// -Not mipmapped
// -Level 0 allocated and then subloaded
glBindTexture( GL_TEXTURE_2D_ARRAY_EXT, texid[3]);
glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 0, GL_RGB8, 4, 4, 3, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 0, 4, 4, 1, GL_RGB, GL_UNSIGNED_BYTE, Red4x4);
glTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 1, 4, 4, 1, GL_RGB, GL_UNSIGNED_BYTE, Green4x4);
glTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 2, 4, 4, 1, GL_RGB, GL_UNSIGNED_BYTE, Blue4x4);

// Fifth texture array (OK)
// -Uncompressed RGB8 data
// -Fully mipmapped (3 levels)
// -Levels allocated and then subloaded
glBindTexture( GL_TEXTURE_2D_ARRAY_EXT, texid[4]);
glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 0, GL_RGB8, 4, 4, 3, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 0, 4, 4, 1, GL_RGB, GL_UNSIGNED_BYTE, Red4x4);
glTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 1, 4, 4, 1, GL_RGB, GL_UNSIGNED_BYTE, Green4x4);
glTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 2, 4, 4, 1, GL_RGB, GL_UNSIGNED_BYTE, Blue4x4);
glTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 1, GL_RGB8, 2, 2, 3, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 1, 0, 0, 0, 2, 2, 1, GL_RGB, GL_UNSIGNED_BYTE, Red4x4);
glTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 1, 0, 0, 1, 2, 2, 1, GL_RGB, GL_UNSIGNED_BYTE, Green4x4);
glTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 1, 0, 0, 2, 2, 2, 1, GL_RGB, GL_UNSIGNED_BYTE, Blue4x4);
glTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 2, GL_RGB8, 1, 1, 3, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 2, 0, 0, 0, 1, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, Red4x4);
glTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 2, 0, 0, 1, 1, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, Green4x4);
glTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 2, 0, 0, 2, 1, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, Blue4x4);

// Sixth texture array (NOT OK)
// -Uncompressed RGB8 data
// -Partially mipmapped (2 levels)
// -Levels allocated and then subloaded
glBindTexture( GL_TEXTURE_2D_ARRAY_EXT, texid[5]);
glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 0, GL_RGB8, 4, 4, 3, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 0, 4, 4, 1, GL_RGB, GL_UNSIGNED_BYTE, Red4x4);
glTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 1, 4, 4, 1, GL_RGB, GL_UNSIGNED_BYTE, Green4x4);
glTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 2, 4, 4, 1, GL_RGB, GL_UNSIGNED_BYTE, Blue4x4);
glTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 1, GL_RGB8, 2, 2, 3, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 1, 0, 0, 0, 2, 2, 1, GL_RGB, GL_UNSIGNED_BYTE, Red4x4);
glTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 1, 0, 0, 1, 2, 2, 1, GL_RGB, GL_UNSIGNED_BYTE, Green4x4);
glTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 1, 0, 0, 2, 2, 2, 1, GL_RGB, GL_UNSIGNED_BYTE, Blue4x4);

No problemo on NV G80. Don’t forget to alloc mipmap chain.

glTexImage3D(…, NULL);
glGenerateMipmaps(…);
foreach image {
foreach image mip level {
glCompressedTexSubImage3D(…, level, …)
}
}

Thanks for your reply. Adding calls to glGenerateMipmap does seem to work. However, I am still confused by something.

One of my examples does the mipmap generation, but with explicit calls to glTexImage3D instead of using glGenerateMipmap. Somehow, doing only the first glTexImage3D followed by a call to glGenerateMipmap works… What’s wrong with the explicit version?

glBindTexture( GL_TEXTURE_2D_ARRAY_EXT, texid[1]);
glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 0, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 4, 4, 3, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);

if( ExplicitMethod )
{
	glTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 2, 2, 3, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
	glTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 2, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 1, 1, 3, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
}
else
{
	glGenerateMipmapEXT( GL_TEXTURE_2D_ARRAY_EXT );
}

glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 0, 4, 4, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTRedBlock);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 1, 4, 4, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTGreenBlock);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 2, 4, 4, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTBlueBlock);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 1, 0, 0, 0, 2, 2, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTRedBlock);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 1, 0, 0, 1, 2, 2, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTGreenBlock);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 1, 0, 0, 2, 2, 2, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTBlueBlock);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 2, 0, 0, 0, 1, 1, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTRedBlock);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 2, 0, 0, 1, 1, 1, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTGreenBlock);
glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 2, 0, 0, 2, 1, 1, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 8, DXTBlueBlock);

IMO, it works in the two cases because:

  1. In the explicit method you allocate memory for the two next mipmap levels and you indeed, set data for the mipmap levels from 0 to 2

  2. In the implicit method, all possible mipmap levels are allocated even if the levels 3 to N may not be used then.

Actually it depends on what you need to do, the explicit method could be dangerous if during rendering mipmap levels higher than 2 are needed and the result would be undefined.

Re-reading my post, I see that I could have been clearer.

Indeed, I agree that both should work! However, the explicit method does not work; it uses whatever was left in memory at some unknown location.

Ideally, I don’t want to pay the extra processing time of glGenerateMipmap since all the mipmaps are already built and I just want to upload their content.

Ah ok, I see. I have never tested it myself, but I agree with you that allocating only levels from 0 to 2 make sense. I will look in the opengl specification if there is any reason that would work; maybe it is an implementation limit.

But, could you be clearer about what fails and in which cases using your explicit method?

I looked at the specification myself and could not find anything. glGenerateMipmap is not even mentioned in the texture_array spec. Maybe I missed something though.

My rendering loop is a screen-aligned quad using only the texture array’s content to compute the shading. When all is ok, I get 3 vertical bars, each using one of the texture in the texture array. When it is not, the output varies between executions. I could not locate any pattern which could reflect the memory arrangement of the mipmap chain.

Following the suggestion from Modus, I am now systematically calling glGenerateMipmap after creating the level 0 mipmap. This sure looks to me like a driver bug, but at least it works. For now, I only allocate 1 or 2 texture arrays when the application starts so it’s not that big of a deal.