Binding to texture units and texture targets

I’m having some trouble understanding how texture binding really works. Of course it is simple when you’re dealing with just one texture, but when more textures come into play, the official documentation isn’t really clear on how the state changes.

First of all, how are the texture bindings represented in the state? I imagine it’s something similar to this, in pseudo-code:

struct texture_state {
  GLuint texture_unit_bindings[UNIT_COUNT][TARGET_COUNT];
  GLenum current_texture_unit;

Then, when glActiveTexture is called something like this probably happens:

void glActiveTexture_impl(struct texture_state *tex_state, GLenum texture) {
  tex_state->current_texture_unit = texture;

So far the docs are clear, but the part that I don’t quite understand is how glBindTexture affects the state. So given it would affect state like this:

void glBindTexture_impl(struct texture_state *tex_state, GLenum target, GLuint texture) {
  tex_state->texture_unit_bindings[tex_state->current_texture_unit][target] = texture;

I suppose that other targets are not updated? What about shader usage, are these two uniform variables referring to different texture targets in the same unit, or should I bind only one texture target to a given texture unit at a time?

uniform sampler2D my_2d_texture;
uniform sampler3D my_3d_texture;
GLint loc_my_2d_texture = glGetUniformLocation(program, "my_2d_texture");
GLint loc_my_3d_texture = glGetUniformLocation(program, "my_3d_texture");
glUniform1i(loc_my_2d_texture, 0);
glUniform1i(loc_my_3d_texture, 0);

That is accurate enough, but honestly I would strongly advise you to pretend that it was just this:

struct texture_state {
  GLuint texture_unit_bindings[TARGET_COUNT];
  GLuint current_texture_unit;

That is, completely forget that it is possible to bind two different textures to the same texture unit. Do not attempt to rely on this behavior at all.

It would be more accurate to say:

void glActiveTexture_impl(struct texture_state *tex_state, GLenum texture) {
  tex_state->current_texture_unit = texture - GL_TEXTURE0;

The texture unit index can exceed any GL_TEXTURE# enumerator. You should always use glActiveTexture(GL_TEXTURE0 + i), where i is the texture unit index.

Or better yet, use glBindTextures (note the “s” at the end) or glBindTextureUnit, from GL 4.4 and 4.5 respectively.

This is why I suggested that you forget that you can bind multiple textures to the same texture unit. But if you insist on knowing how this works:

Samplers in GLSL are typed. Each sampler type maps to a specific texture target. So for a given sampler uniform, the value assigned to the uniform tells which texture unit to pull data from, and the type of the sampler tells which target it gets data from.

However, OpenGL explicitly forbids you from assigning two uniforms of different sampler types to the same texture unit. I mean, you can do it, but if you try to render with that program, you either get GL_INVALID_OPERATION or undefined behavior (I’ve forgotten which).

This is why you should forget that this is possible; you can’t use it to do anything useful. So just pretend that every bind call to a texture unit also unbinds every other target from that texture unit.

That makes much more sense now, thanks. I was asking if it is possible because my GL wrapper optimizes away unnecessary command calls (as sending a command to the GPU is way more expensive than a simple if check), and I need to know what sort of state do I need to store client-side. My final wrapper isn’t going to permit this invalid behavior of assigning different targets to a single texture unit in the end anyways.

It seems pretty clearly described in “Textures (state per texture unit)”, Table 23.17 in GL4.6.

Correct. You can easily verify this by introspecting the GL_TEXTURE_BINDING_* state described in that table.

As Alfonse alluded, sampler uniforms can’t refer to the same unit if they have different types. So while you can bind (and TexImage etc) 2D and 3D to unit zero simultaneously, if you try to sample from them, it is an INVALID__OPERATION at draw time. See glValidateProgram and the relevant condition in the specification: “Any two active samplers in the set of active program objects are of different types, but refer to the same texture image unit.”

In the ancient pre-shader world, the glEnable state of the texture targets determined which target is actually sampled on each unit.