152

From what I gather, glActiveTexture sets the active "texture unit". Each texture unit can have multiple texture targets (usually GL_TEXTURE_1D, 2D, 3D or CUBE_MAP).

If I understand correctly, you have to call glActiveTexture to set the texture unit first (initialized to GL_TEXTURE0), and then you bind (one or more) "texture targets" to that texture unit?

The number of texture units available is system dependent. I see enums for up to 32 in my library. I guess this essentially means I can have the lesser of my GPU's limit (which I think is 16 8) and 32 textures in GPU memory at any one time? I guess there's an additional limit that I don't exceed my GPU's maximum memory (supposedly 1 GB).

Am I understanding the relationship between texture targets and texture units correctly? Let's say I'm allowed 16 units and 4 targets each, does that mean there's room for 16*4=64 targets, or does it not work like that?

Next you typically want to load a texture. You can do this via glTexImage2D. The first argument of which is a texture target. If this works like glBufferData, then we essentially bind the "handle"/"texture name" to the texture target, and then load the texture data into that target, and thus indirectly associate it with that handle.

What about glTexParameter? We have to bind a texture target, and then choose that same target again as the first argument? Or does the texture target not need to be bound as long as we have the correct active texture unit?

glGenerateMipmap operates on a target too...that target has to still be bound to the texture name for it to succeed?

Then when we want to draw our object with a texture on it, do we have to both choose an active texture unit, and then a texture target? Or do we choose a texture unit, and then we can grab data from any of the 4 targets associated with that unit? This is the part that's really confusing me.

0

3 Answers 3

302

All About OpenGL Objects

The standard model for OpenGL objects is as follows.

Objects have state. Think of them as a struct. So you might have an object defined like this:

struct Object
{
    int count;
    float opacity;
    char *name;
};

The object has certain values stored in it and it has state. OpenGL objects have state too.

Changing State

In C/C++, if you have an instance of type Object, you would change its state as follows: obj.count = 5; You would directly reference an instance of the object, get the particular piece of state you want to change, and shove a value into it.

In OpenGL, you don't do this.

For legacy reasons better left unexplained, to change the state of an OpenGL object, you must first bind it to the context. This is done with some from of glBind* call.

The C/C++ equivalent to this is as follows:

Object *g_objs[MAX_LOCATIONS] = {NULL};    
void BindObject(int loc, Object *obj)
{
  g_objs[loc] = obj;
}

Textures are interesting; they represent a special case of binding. Many glBind* calls have a "target" parameter. This represents different locations in the OpenGL context where objects of that type can be bound. For example, you can bind a framebuffer object for reading (GL_READ_FRAMEBUFFER) or for writing (GL_DRAW_FRAMEBUFFER). This affects how OpenGL uses the buffer. This is what the loc parameter above represents.

Textures are special because when you first bind them to a target, they get special information. When you first bind a texture as a GL_TEXTURE_2D, you are actually setting special state in the texture. You are saying that this texture is a 2D texture. And it will always be a 2D texture; this state cannot be changed ever. If you have a texture that was first bound as a GL_TEXTURE_2D, you must always bind it as a GL_TEXTURE_2D; attempting to bind it as GL_TEXTURE_1D will give rise to an error (while run-time).

Once the object is bound, its state can be changed. This is done via generic functions specific to that object. They too take a location that represents which object to modify.

In C/C++, this looks like:

void ObjectParameteri(int loc, ObjectParameters eParam, int value)
{
  if(g_objs[loc] == NULL)
    return;

  switch(eParam)
  {
    case OBJECT_COUNT:
      g_objs[loc]->count = value;
      break;
    case OBJECT_OPACITY:
      g_objs[loc]->opacity = (float)value;
      break;
    default:
      //INVALID_ENUM error
      break;
  }
}

Notice how this function sets whatever happens to be in currently bound loc value.

For texture objects, the main texture state changing functions are glTexParameter. The only other functions that change texture state are the glTexImage functions and their variations (glCompressedTexImage, glCopyTexImage, the recent glTexStorage). The various SubImage versions change the contents of the texture, but they do not technically change its state. The Image functions allocate texture storage and set the texture's format; the SubImage functions just copy pixels around. That is not considered the texture's state.

Allow me to repeat: these are the only functions that modify texture state. glTexEnv modifies environment state; it doesn't affect anything stored in texture objects.

Active Texture

The situation for textures is more complex, again for legacy reasons best left undisclosed. This is where glActiveTexture comes in.

For textures, there aren't just targets (GL_TEXTURE_1D, GL_TEXTURE_CUBE_MAP, etc). There are also texture units. In terms of our C/C++ example, what we have is this:

Object *g_objs[MAX_OBJECTS][MAX_LOCATIONS] = {NULL};
int g_currObject = 0;

void BindObject(int loc, Object *obj)
{
  g_objs[g_currObject][loc] = obj;
}

void ActiveObject(int currObject)
{
  g_currObject = currObject;
}

Notice that now, we not only have a 2D list of Objects, but we also have the concept of a current object. We have a function to set the current object, we have the concept of a maximum number of current objects, and all of our object manipulation functions are adjusted to select from the current object.

When you change the currently active object, you change the entire set of target locations. So you can bind something that goes into current object 0, switch to current object 4, and will be modifying a completely different object.

This analogy with texture objects is perfect... almost.

See, glActiveTexture does not take an integer; it takes an enumerator. Which in theory means that it can take anything from GL_TEXTURE0 to GL_TEXTURE31. But there's one thing you must understand:

THIS IS FALSE!

The actual range that glActiveTexture can take is governed by GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS. That is the maximum number of simultaneous multitextures that an implementation allows. These are each divided up into different groupings for different shader stages. For example, on GL 3.x class hardware, you get 16 vertex shader textures, 16 fragment shader textures, and 16 geometry shader textures. Therefore, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS will be 48.

But there aren't 48 enumerators. Which is why glActiveTexture doesn't really take enumerators. The correct way to call glActiveTexture is as follows:

glActiveTexture(GL_TEXTURE0 + i);

where i is a number between 0 and GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS.

Rendering

So what does all of this have to do with rendering?

When using shaders, you set your sampler uniforms to a texture image unit (glUniform1i(samplerLoc, i), where i is the image unit). That represents the number you used with glActiveTexture. The sampler will pick the target based on the sampler type. So a sampler2D will pick from the GL_TEXTURE_2D target. This is one reason why samplers have different types.

Now this sounds suspiciously like you can have two GLSL samplers, with different types that use the same texture image unit. But you can't; OpenGL forbids this and will give you an error when you attempt to render.

6
  • 14
    Wow! Yet another wonderful answer - thanks Nicol! I especially like that paragraph about a 2D texture always being a 2D texture. I'm building a wrapper around some of these things now, and I wasn't sure if I should leave that open to change. And the part about GL_TEXTURE0 + i -- I was meaning to inspect the enum values to see if that was valid or not. And the last paragraph - didn't know if that was legal or not. Excellent! I'm bookmarking all your answers so I can refer to them again.
    – mpen
    Jan 17, 2012 at 2:01
  • Is glGenerateMipmap part of the set of functions that change state or is it part of the set of functions that change content? Jul 27, 2014 at 7:47
  • Any idea why openGL forbids different types of GLSL samplers to the same texture unit? Is it a legacy reason?
    – dev_nut
    Oct 26, 2014 at 19:13
  • @dev_nut: You have it backwards. The "legacy reason" is what makes the API seem like it should be allowed. That is, the texture's type being part of the binding target. Note that more modern texture binding functions like glBindTextures (note the "s") don't take a texture target as a parameter. That's how the hardware works; a texture unit is a piece of hardware that accesses a texture. A single texture. The API was simply poorly designed originally. Dec 23, 2015 at 14:13
  • 1
    Never explain something by saying how it is, and then saying "but that's completely false". There's no more guaranteed way to make people learn the wrong thing. Jan 27, 2016 at 21:20
23

I'll give it a try! All this is not that complicated, just a question of terms, hope I'll make myself clear.


You can create roughly as many Texture Objects as there is available memory in your system. These objects hold the actual data (texels) of your textures, along with parameters, provided by glTexParameter (see FAQ).

When being created, you have to assign one Texture Target to one texture object, which represents the type of the texture (GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE, ...).

These two items, texture object and texture target represent the texture data. We'll come back to them later.

Texture units

Now, OpenGL provides an array of texture units, that can be used simultaneously while drawing. The size of the array depends of the OpenGL system, yours has 8.

You can bind a texture object to a texture unit to use the given texture while drawing.

In a simple and easy world, to draw with a given texture, you'd bind a texture object to the texture unit, and you'd do (pseudocode):

glTextureUnit[0] = textureObject

As GL is a state machine, it, alas, does not work this way. Supposing that our textureObject has data for the GL_TEXTURE_2D texture target, we'll express the previous assignment as:

glActiveTexture(GL_TEXTURE0);                   // select slot 0 of the texture units array
glBindTexture(GL_TEXTURE_2D, textureObject);    // do the binding

Note that GL_TEXTURE_2D really depends on the type of the texture you want to bind.

Texture objects

In pseudo code, to set texture data or texture parameters, you'd do for example:

setTexData(textureObject, ...)
setTexParameter(textureObject, TEXTURE_MIN_FILTER, LINEAR)

OpenGL can't directly manipulate texture objects, to update/set their content, or change their parameters, you have to first bind them to the active texture unit (whichever it is). The equivalent code becomes:

glBindTexture(GL_TEXTURE_2D, textureObject)       // this 'installs' textureObject in texture unit
glTexImage2D(GL_TEXTURE_2D, ...)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)

Shaders

Shaders have access to all the texture units, they don't care about the active texture.

Sampler uniforms are int values representing the index of the texture unit to use for the sampler (and not the texture object to use).

So you have to bind your texture objects to the units you want to use.

The type of the sampler will do the match with the texture target that is used in the texture unit: Sampler2D for GL_TEXTURE_2D, and so on...

2
  • One thing I don't understand. Let's assume I have some texture and it is used in many shaders on different texture units. Let's assume I want to change texture filtering on run time. What texture unit should I use? Can I change texture state on Unit 0 and then use that texture on different unit? Mar 16, 2015 at 9:54
  • @majakthecoder In my answer, I consider the filtering as a property of the texture object - which means you can't change it specifically in a texture unit. Depending of the flavor of OpenGL you're targetting, you may be able to sampler objects to solve this problem (opengl.org/wiki/Sampler_Object), otherwise, you may have to duplicate the texture object, to have multiple simultaneous filterings.
    – rotoglup
    Mar 16, 2015 at 12:55
12

Imagine the GPU like some paint processing plant.

There are a number of tanks, which delivers dye to some painting machine. In the painting machine the dye is then applied to the object. Those tanks are the texture units

Those tanks can be equipped with different kinds of dye. Each kind of dye requires some other kind of solvent. The "solvent" is the texture target. For convenience each tank is connected to some solvent supply, and but only one kind of solvent can be used at a time in each tank. So there's a valve/switch TEXTURE_CUBE_MAP, TEXTURE_3D, TEXTURE_2D, TEXTURE_1D. You can fill all the dye types into the tank at the same time, but since only one kind of solvent goes in, it will "dilute" only the kind of dye matching. So you can have each kind of texture being bound, but the binding with the "most important" solvent will actually go into the tank and mix with the kind of dye it belongs to.

And then there's the dye itself, which comes from a warehouse and is filled into the tank by "binding" it. That's your texture.

9
  • 2
    Kind of a strange analogy...I'm not sure it really clears anything up. Especially the part about the "diluting" and "most important solvent". You're saying if I bind both a 2d texture and 3d texture, I can only use one of them, or what? Which one would be deemed most important?
    – mpen
    Jan 16, 2012 at 22:51
  • 2
    @Mark: Well, I was trying to speak in terms of a painter who works with literal dye (say oil based and water based). Anyway, yes if you bind and enable multiple texture targets there's precedence: CUBE_MAP > 3D > TEXTURE_ARRAY > 2D > 1D.
    – datenwolf
    Jan 16, 2012 at 23:21
  • 1
    Neat! I didn't know about the precedence. Makes more sense now that I know only one texture target can be used per texture unit.
    – mpen
    Jan 17, 2012 at 2:06
  • 1
    @legends2k: Well, now it's getting interesting. Are we talking about the core or the compatibility profile. Do we assume ideal, or buggy drivers. In theory the type of the uniform selects which target of the texture unit to select. In practice this happens in core profile. In compatibility profile expect some buggy drivers to present you with the all white default texture if the preceding target of the texture unit doesn't match the type of the sampler.
    – datenwolf
    Sep 6, 2012 at 11:49
  • 1
    @legends2k: Also, think about what would happen if there were a 2D and a 3D texture bound to the same unit and you had a 2D and a 3D sampler uniform, which you bind to the same unit? You can trigger all kinds of weird driver bugs with this. In practice thinking in the old fixed function precedence model keeps your mind sane and your program working, because that's how most drivers will behave in a predictable way.
    – datenwolf
    Sep 6, 2012 at 11:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.