Array Texture confusion

I want to make an array-texture from 2D textures with mipmaps, but Im confused.

This is one of the “tutorials” I looked at:

Here they use glTexStorage3D to allocate storage but I saw other code where they used glTexImage3D.
In these functions what is specified by the parameters “depth” and “level”?

Lets say I want an array from 2 textures which have 3 mip-map levels. Does that mean I have to call glTexSubImage3D six times? Why does this function have “depth” and “level” parameters too?

glTexImage*() is used when mutable textures are created or you are confined to GL 3.3. glTexStorage*() is used to make storage for immutable textures and requires GL 4.2+.
It is a common story for all textures, not texture arrays only.

“Level” specifies a layer in a texture array. Since there is no special function for texture arrays, they use higher order texture storage. That’s why 2D texture array uses 3D texture interface.

For more information take a look at the spec.

See also Microsoft’s D3D documentation, where they have a simple illustration that explains it better than 1000 words in the GL spec ever will.

No, “level” specifies the mip level, as usual. The Z direction, aka depth, zoffset, etc. are the ones that specify the layer.

I’m sorry for the mistake! :frowning:
I didn’t take a look at the function header (usually I interchange both terms for the same thing). Mea culpa…

Thanks for the answers! I still dont understand the difference here between glTexImage and glTexSubImage. Does glTexImage define only 1 mip-map level for the given texture? It says “glTexImage3D — specify a three-dimensional texture image” but I see code that calls this many times for the same texture.
So how many times should I call these functions for a 2-layer 3-mip-map-level texture array?

The API differences here are about mutability and texture completeness. It helps to understand those concepts.

TexImage defines the geometry of one mipmap level in a texture. That one level can be 1, 2, or 3 dimensional, depending on the texture target (TEXTURE_2D_ARRAY is a 3 dimensional target.) Also, if you pass a non-NULL <pixels> argument, you fill the geometry you just defined with the pixels you provide. These are two separate actions: defining the geometry (allocating memory), and then transferring pixels (writing data to that memory).

TexSubImage only transfers pixels, into a single mipmap level. The geometry must have been previously defined.

Now, here is a very common mistake made by every beginner OpenGL programmer:

    ...gen and bind texture
    glTexImage2D(GL_TEXTURE_2D, level0... width, height... pixels)
    ...draw with that texture

The result is all black, because this texture is incomplete. What is “complete”? It means a complex combination of the mipmap geometry, the mipmap image formats, the sampler filtering modes, and sampler wrap modes are all compatible and will make sense when the texture is sampled in a shader. Read the “Texture Completeness” chapter for full details. The short answer is: if you want mipmap filtering, you must define all levels, each one being half the size of the previous one. If you don’t want mipmap filtering, turn it off.

Textures defined by TexImage are mutable: after defining them (and drawing with them), there is nothing stopping you from re-defining the geometry of the same texture object. That’s fine, but it’s pretty easy to make a programming mistake and leave the texture in an incomplete state (for example, if you change the level0 size, and forget to change the rest of the levels. Or make the level0 different from the other levels. Or mix-and-match multiple sampler objects with multiple textures, and some wind up with incompatible filtering.) And, because defining a complete mipmap requires multiple TexImage calls, the texture will always be temporarily incomplete, during those few calls. This is a headache for driver implementors.

TexStorage solves most of those problems, by defining the entire mipmap geometry (allocating memory) in one API call. And it makes the texture immutable so you can’t accidentally break it afterwards. However, it does not transfer any pixel data. You must use TexSubImage to do that.

So, with those definitions, these are (roughly) equivalent:

  1. TexImage(…level0… pixels);
  2. TexImage(…level0… NULL); TexSubImage(…level0… pixels);
  3. TexStorage(…1…); TexSubImage(…level0… pixels); // this also ensures mipmap completeness and makes the texture immutable

And to re-answer your original question:

Lets say I want an array from 2 textures which have 3 mip-map levels. Does that mean I have to call glTexSubImage3D six times? Why does this function have “depth” and “level” parameters too?

“Level” always means the mipmap level. “Depth” only applies to 3D targets. It means the number of “slices” (or “layers”) in the array.
You will most likely have to transfer pixels six separate times, but it depends how your pixels are laid out in memory. Here is one way to do it:

TexStorage3D(...3... W, H, 2); // allocates W x H x 2 level0, W/2 x H/2 x 2 level1, W/4 x H/4 x 2 level2.  Contents are undefined at this point.
TexSubImage3D(...level0, 0, 0, 0, W, H, 1... lod0_slice0_pixels);
TexSubImage3D(...level0, 0, 0, 1, W, H, 1... lod0_slice1_pixels);
TexSubImage3D(...level1, 0, 0, 0, W/2, H/2, 1... lod1_slice0_pixels);
TexSubImage3D(...level1, 0, 0, 1, W/2, H/2, 1... lod1_slice1_pixels);
TexSubImage3D(...level2, 0, 0, 0, W/4, H/4, 1... lod2_slice0_pixels);
TexSubImage3D(...level2, 0, 0, 1, W/4, H/4, 1... lod2_slice1_pixels);   // all slices of all mipmaps now transferred

With TexStorage, this is mipmap complete, even if W/4 != 1 or H/4 != 1. If you use TexImage, you would either have to provide all the mipmap levels down to 1x1xD, or change TEXTURE_MAX_LEVEL. TexStorage is generally a better API, but it requires fairly recent drivers and isn’t available everywhere.

Note that when you transfer a set of 3D pixels, the default PixelStorage settings will assume that the pixels for slice0 are immediately followed in memory by slice1. If that is true, then you could collapse the above sequence into only three TexSubImage calls, transferring two slices at a time:

TexSubImage3D(...level0, 0, 0, 0, W, H, 2... lod0_slice0and1_pixels);
TexSubImage3D(...level1, 0, 0, 0, W/2, H/2, 2... lod1_slice0and1_pixels);
TexSubImage3D(...level2, 0, 0, 0, W/4, H/4, 2... lod2_slice0and1_pixels);

…but if you started with 2D textures, then your pixels probably aren’t arranged like that.

And finally, if you don’t care about the exact mipmap contents, then you could just transfer level0 and call glGenerateMipmap to automatically generate the rest by downsampling level0.

Thanks for the clarification! I see that glTexStorage is superior, I just want to stick to OpenGL3.3 for now.
I didnt know about glGenerateMipmap. Is there any reason not to use it and upload the mip-maps manually instead?

Most of the time the automatically generated mipmaps are “good enough”.

Certain applications want custom-generated mipmaps (extra sharpening, or specialized fog/colorization/alpha masking) so need to provide all of the levels.
Applications using compressed texture formats also usually provide all of the levels since the compression process is usually better done off-line.

Hey, I’m struggling with texture arrays, too.
Is there anything wrong with my setup?
In my fragment-shader my Sampler2DArray resolves to null and therefore i get a complete black screen.

ByteBuffer texels = ByteBuffer.allocateDirect(data.length);
id = glGenTextures();

glBindTexture(GL_TEXTURE_2D_ARRAY, id);


glTexStorage3D(GL_TEXTURE_2D_ARRAY, mipmaps, GL_RGB, width, height, textures);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, 1, GL_RGB, GL_UNSIGNED_BYTE, texels);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, width, height, 1, GL_RGB, GL_UNSIGNED_BYTE, texels);

i hope you can help me! :slight_smile:

That usage of glTexStorage3D is wrong, and should throw GL_INVALID_ENUM.

Because: another problem TexStorage solves is eliminating ambiguous <internalformat> enums.

[QUOTE=arekkusu;1258273]That usage of glTexStorage3D is wrong, and should throw GL_INVALID_ENUM.

Because: another problem TexStorage solves is eliminating ambiguous <internalformat> enums.[/QUOTE]

Thank you very much!
Everything is working now! :smiley: