How to share a texture between multiple shader programs?

I’m using OpenGL 3.3 and GLSL 3.3.

Is it possible to share textures across multiple shader programs so I don’t have to upload the texture to the GPU more than once?
For example, let’s say I have one 2D noise texture and two objects to render. Each object has its own associated vertex+fragment shader. In both of the fragment shaders, it uses the same noise texture (uniform sampler2D) and the texture parameters do not change.

At the moment, I have to call glTexImage2D twice (once for each shader program) to upload the texture to the GPU. Here is some simplied example code:


Image myImage;
ShaderProgram shaderProgram[2];

init();
For every frame, call draw();


void init(){
   ... Load image from disk ...

   ... Compile and install shader program 1. ...
   glUseProgram(shaderProgram[0].id);
   setTexture(myImage, 0);

   ... Compile and install shader program 2. ...
   glUseProgram(shaderProgram[1].id);
   setTexture(myImage, 1);
}

void setTexture( myImage, shaderProgramIndex ){
   glGenTextures(1, &shaderProgram[shaderProgramIndex].textureID);
   glActiveTexture(GL_TEXTURE0);
   glBindTexture(GL_TEXTURE_2D, shaderProgram[shaderProgramIndex].textureID);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, myImage.width(), myImage.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, myImage.data());

   GLint loc = glGetUniformLocation(shaderProgram[shaderProgramIndex].id, "myTexture");
   glUniform1i(loc, 0);
}

void draw(){ 
   // Draw object 1.
   glUseProgram(shaderProgram[0].id);
   glBindTexture(GL_TEXTURE_2D, shaderProgram[0].textureID);
   glBindVertexArray(vao0);
   glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo0);
   glDrawElements(GL_TRIANGLES, ...);

   // Draw object 2.
   glUseProgram(shaderProgram[1].id);
   glBindTexture(GL_TEXTURE_2D, shaderProgram[1].textureID);
   glBindVertexArray(vao1);
   glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo1);
   glDrawElements(GL_TRIANGLES, ...);

   swapBuffers();
}

I’ve looked into Uniform Buffer Objects, Sampler Objects (glGenSamplers), and glTextureView. I read that UBOs don’t work with textures so they won’t help. I don’t think Sampler objects are what I need since the texture parameters don’t change. I think glTextureView is what I need but this is only available starting with OpenGL 4.3. My graphics card can only support up to OpenGL 3.3.

Combining the shader programs is not an option. How to I share a texture between more than one shader program?

Your code is very confused. Your “setTexture” function is not something you should call during rendering. That’s initialization code. It creates the texture object and uploads image data into it.

It has nothing to do with shaders using the texture. Well, outside of the fact that it needs to happen before trying to render with a shader that uses the texture. But that has nothing to do with shaders; it’s just you need to set up any kind of object before you try to render with it. Just like you can’t live in a house until it’s been built. You initialize the texture before you render, just like you initialize your programs before you try to render with them.

The only thing you have to do when rendering is to bind the texture to the proper texture image unit. This of course assumes that you have assigned the texture image unit to the sampler uniform in the shader.

Yes, I’m aware that I don’t need to call ‘setTexture’ on every frame. That was not the point. I was just trying to put up some simplified code to show that I need to call glTexImage2D for the same image for every shader program. I’ve modified the example code to make it less confusing.

Your “setTexture” function does two things (one I didn’t notice the first time): it creates a texture object, and it stores that texture object in a ShaderProgram.

ShaderProgram isn’t OpenGL’s stuff; it’s yours. You aren’t sharing a texture at all; you’re creating two separate textures (which just so happen to have the same data), and then storing each of those texture objects next to two different program objects.

If you need both shaders to reference the same texture, then… do that. You need a “createTexture” function, whose sole purpose is to create a texture object. Then you need a “setTexture” function that sets a texture in a particular ShaderProgram (or just access the appropriate member of the struct).

Like this:


... Load image from disk ...
GLuint texture = createTexture(myImage);
 
... Compile and install shader program 1.
glUseProgram(shaderProgram[0].id);
shaderProgram[0].textureId = texture;

... Compile and install shader program 2. ...
glUseProgram(shaderProgram[1].id);
shaderProgram[1].textureId = texture;

Furthermore, it’s not a good idea to directly associate textures with the programs that use them like this. Textures and programs are not directly associated. And it’s perfectly reasonable to use the same program with different textures. Indeed, it’s not uncommon to have far more texture changes than shader changes.

Thank you. I was under the wrong impression that textures could only be set up after a call to glUseProgram() which made me think that they were bound to the shader.