3
\$\begingroup\$

In OpenGL, most shader uniform variables are easy, a float or a vec3 or whatever is associated with a shader program. But samplers are different, we associate a texture unit to the shader program, and associate a texture with a texture unit.

To change a texture on a shader program, there are two approaches:

  1. reassign the texture unit, or
  2. use the same texture unit with a new texture.

Codewise:

glUseProgram(theProgram);

// way 1 setup
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, texture2);

// way 1 assign before each draw
glUniform1i(uniformLocation, 1);
...draw...
glUniform1i(uniformLocation, 2);
...draw...

or

// way 2 setup
glUniform1i(uniformLocation, 1);

// way 2 assign before each draw
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE2D, texture1);
...draw...
glBindTexture(GL_TEXTURE2D, texture2);
...draw...

I profiled both, and got inconclusive results. On desktop (mac pro), "Way 1" is much slower. On laptop (mac retina), they're about the same.

Both ways work, so neither is "wrong"... so perhaps this question is loose. Best would be to profile every video card, but I cannot.

Is there a recommended approach for managing textures & texture units?

(Or, at the risk of making the question even vaguer, is there some mental model I should have for how textures, texture units, and shader programs interact which makes it obvious why Way 2 might be faster on some platforms?)

\$\endgroup\$
1
\$\begingroup\$

You only have a set of 16 possible textures per-shader stage, so way 1 may get out of hand quickly. You have up to 80 possible unique binding locations in GL4 (16 * 5 stages = 80) and 48 in GL3 (16 * 3), but you can only use 16 of those in a single shader invocation.

In truth, bindless textures, array textures and texture atlases have all been designed to make this sort of thing more efficient. Instead of breaking up your draw calls to change something as trivial as a single texture, those each allow you a way of working around that. I listed them in order of hardware requirements, by the way - with texture atlases requiring zero special hardware and bindless textures requiring a late model GPU.

\$\endgroup\$
  • \$\begingroup\$ Just for further context of why I care about the question as written: My library attempts to expose a model which includes materials (shaders), textures, and frame buffers (and geometry). Behind the scenes it uses some of its own textures as well (per-part matrices and other per-part attributes, dynamically-built texture atlases, &c). For optimization, I try to minimize state changes. SO. Should I attempt to do Least-Recently-Used harvesting of texture units, or just rebind the textures before each draw? :-) But also, just trying to understand why glUniform1i on a sampler costs. \$\endgroup\$ – david van brink Mar 15 '15 at 20:42
  • \$\begingroup\$ (All of which is to say, the code shown above is only to illustrate the question, not because I'm reassigning textures for each side of my cube. It comes up currently when I want to reuse a post-processing step with different parameters on a fresh input, for example.) \$\endgroup\$ – david van brink Mar 15 '15 at 20:48
  • 1
    \$\begingroup\$ @davidvanbrink: Well, the actual act of setting a uniform has virtually no overhead. Of all the states you can change, it is quite possibly the cheapest, there's no validation or anything like that (where as binding a texture has quite a bit of validation). An LRU replacement policy should work very well. I am at a loss as to why the first approach would actually be measurably slower, unless you are doing something silly like querying the uniform location by name each time you assign it a value. \$\endgroup\$ – Andon M. Coleman Mar 15 '15 at 20:56
  • 1
    \$\begingroup\$ @davidvanbrink: There is one other possibility you can consider on newer GPUs. You can declare arrays of sampler2D uniforms in modern GLSL, and you could use a separate uniform to index this array to pick the relevant sampler at run-time. This is tremendously useful for instancing, and may help in your situation as well. \$\endgroup\$ – Andon M. Coleman Mar 15 '15 at 21:00
  • \$\begingroup\$ sampler2D array! I'll play with that. Yes -- as it happens my code currently does look up uniform by name each loop, but have determined that's not the bulky issue. (Although it is certainly something to squeeze out at some point, ++.) \$\endgroup\$ – david van brink Mar 15 '15 at 22:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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