Sunday, 17 January 2016

glActiveTexture

I'm just taking a quick sidestep from my tutorial series to spend a few words on glActiveTexture and when to a bind texture which may have been unclear as far as this tutorial has gone.

OpenGL is a tad strange when it comes to textures. One would think that glGenTextures would return an ID that we can use whenever we identify a texture and OpenGL would sort itself out. It makes sense that we can only do operation like loading on one texture at a time and therefor need to bind it, but the rest seems a bit silly.

glGenTextures gives us IDs that allow OpenGL to identify which texture we're working with (e.g. texture objects). In theory you can have as many texture objects as you like provided you've got enough memory to load textures into.

But from the early days of OpenGL you could only use a limited number of textures at any given time simultaneously. Usually not much of a problem as most shaders only use a single texture, and more complex ones may use 3 or 4. You could have hundreds of texture objects loaded, but you'd only use a select few textures at any given time.

You could look at the set of active textures as a fixed size register and each entry in this register is identified with the GL_TEXTUREn constants (also called texture units).
So GL_TEXTURE0 is our first active texture, GL_TEXTURE1 our second, GL_TEXTURE2 our third, etc. These constants generally go up to GL_TEXTURE16 even if more are supported but you can simply do GL_TEXTURE0 + 16 to access GL_TEXTURE17.

When we want to do something with textures we therefor first need to select one of these texture units and then bind the texture object we wish to use as follows:
glActiveTexture(GL_TEXTURE0); // make our first texture unit active
glBindTexture(GL_TEXTURE_2D, TextureId); // bind the ID we got from glGenTextures
This binds our texture object id to our first texture unit. Initially as we're loading textures, we'd now configure the texture, load the texture data, etc. All these types of actions modify the texture itself, that we are using our first texture unit is simply a necessity but binding a different texture will not effect the settings and/or data we changed on our current texture.
This is why you often see in loading code that all textures are loaded while our first texture unit is active. It is often simply not even set as our first texture unit is the active one by default. In some extreme cases this can be a problem if other bits of code do not expect the texture bound to a particular unit to change. I would argue that is bad coding and you should specify the texture unit you wish to use and bind the correct texture object, not assume either is set already.

Once we're in our render loop our units become important. If we want to use 4 textures at the same time we can't simply bind one after the other. Each would overwrite the binding of the next. We need to bind our 4 textures to 4 individual units like so:
glActiveTexture(GL_TEXTURE0); // make our first texture unit active
glBindTexture(GL_TEXTURE_2D, TextureId[0]); // bind our first texture object
glActiveTexture(GL_TEXTURE1); // make our second texture unit active
glBindTexture(GL_TEXTURE_2D, TextureId[1]); // bind our second texture object
glActiveTexture(GL_TEXTURE2); // make our third texture unit active
glBindTexture(GL_TEXTURE_2D, TextureId[2]); // bind our third texture object
glActiveTexture(GL_TEXTURE3); // make our fourth texture unit active
glBindTexture(GL_TEXTURE_2D, TextureId[3]); // bind our fourth texture object
Now our four textures are all active. It will come as no surprise that it is the texture units, not the IDs generated by glGenTextures, which we subsequently use in our shaders:
glUniform1i(samplerId1, 0); // use our first texture unit for sampler 1
glUniform1i(samplerId2, 1); // use our second texture unit for sampler 2
glUniform1i(samplerId3, 2); // use our third texture unit for sampler 3
glUniform1i(samplerId4, 3); // use our fourth texture unit for sampler 4
Put the two examples together and a real life example may look something like this:
glActiveTexture(GL_TEXTURE0); // make our first texture unit active
glBindTexture(GL_TEXTURE_2D, ColorTextureID); // bind our color texture object
glUniform1i(ColorSamplerID, 0); // use our first texture unit for our color sampler

glActiveTexture(GL_TEXTURE1); // make our second texture unit active
glBindTexture(GL_TEXTURE_2D, NormalMapID); // bind our normal map texture object (bump map)
glUniform1i(NormalMapSamplerID, 1); // use our second texture unit for our normal map sampler

glActiveTexture(GL_TEXTURE2); // make our third texture unit active
glBindTexture(GL_TEXTURE_2D, EnvMapID); // bind our environment map texture object
glUniform1i(EnvMapSamplerID, 2); // use our second texture unit for our environmnet map sampler

// it is considered good sport to make our first texture unit the active one again:
glActiveTexture(GL_TEXTURE0);
It's also considered good form once you are done rendering to unbind all the textures:
glActiveTexture(GL_TEXTURE0); // make our first texture unit active
glBindTexture(GL_TEXTURE_2D, 0); // unbind
glActiveTexture(GL_TEXTURE1); // make our second texture unit active
glBindTexture(GL_TEXTURE_2D, 0); // unbind
etc...

Like I mentioned before, this may seem a little silly and bloated, why not just use our texture object IDs directly in our shaders?

There is a little more to this. There is a cost to having textures active as it enables sampling from these textures.
Having more textures active then needed may claim resources unnecessarily and slow things down especially once you hit cache limits.
At the same time, binding a new texture can result in overhead if that texture is currently not loaded into faster cache memory and needs to be brought forward. 

It can be a bit of a balancing act however I'd try and abide by two rules:
- have only the textures active you need for your current shader
- minimize the number of times you need to change which textures are bound by ordering what you draw based on the shader and the texture(s) used.

This last remark is important because it feels counter productive. Especially in the past where graphics cards were much simpler or in the days that everything was done on the CPU often objects were ordered by distance to draw objects closer to the viewer first especially after Z-buffers were added into the mix. It meant the largest number of fragments could be discarded and you would save many costly calculations.

While this definately still holds true and it makes sense to draw particularly large things first (so for an FPS game, draw your ground first, your objects next and your sky last), todays graphics cards are multicore beasts making the most of parallel processing with the result that your carefully picked order of drawing objects may end up being completely distorted as polygons you've carefully put later on your list are drawn first by a core that happens to be idle at the time.
Sorting your objects to make sure you draw objects that use the same shader and same/similar textures together may result in better performance improvements then using a Z-sort.

So hope that clears up a few things that can be a little confusing.

I'll be back to my tutorials soon, I've finished about 95% of the 3D object loading code but I've yet to do a lot of cleanup of the code as it is a lot. I think I've nearly doubled the size of my example code :)
In the process I also found a few dumb mistakes, mentioned them in my previous post and a comment I added to the last part of the tutorial. I want to make sure the next version has as little bugs as possible :)

3 comments:

  1. Finally a really great explanation for some strange (legacy) concepts. Thanks a lot!

    ReplyDelete
  2. good explanation ..thanks

    ReplyDelete
  3. Thanks, nice article and well explained.

    ReplyDelete