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:
- TexImage(…level0… pixels);
- TexImage(…level0… NULL); TexSubImage(…level0… pixels);
- 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.