Jeff Saremi
Jeff Saremi

Reputation: 3024

Right order to specifying framebuffer and binding uniform textures?

Is there a sequence to setting the output of a program and binding textures to uniforms in the fragment shader?

I have the following code. If I place the lines containing "attachFrameBuffer" after the last "g.uniform1i()" call, I get the error:

There is no texture bound to the unit 1.

But if I leave them where they are then everything is fine. This worries me that there is more initialization that I probably have missed.

gl.useProgram(program);

// Create and bind a framebuffer
var outputTexture = this.makeTexture(gl.FLOAT, null);
this.attachFrameBuffer(outputTexture);

gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, aTexture);
gl.uniform1i(AHandle, 0);

gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, bTexture);
gl.uniform1i(BHandle, 1);

gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

and the code for makeTexture:

   texture = gl.createTexture();
   // Bind the texture so the following methods effect this texture.
   gl.bindTexture(gl.TEXTURE_2D, texture);
   gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
   gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
   gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
   gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
   // Pixel format and data for the texture
   gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, type, data);
   // Unbind the texture.
   gl.bindTexture(gl.TEXTURE_2D, null);

code for attachFrameBuffer():

   frameBuffer = gl.createFramebuffer();
   gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
   gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); 

Upvotes: 1

Views: 358

Answers (1)

user128511
user128511

Reputation:

Textures are bound to "texture units". Texture units are global state. You can imagine them like this

glState = {
  activeTextureUnit: 0,
  textureUnits: [
    { TEXTURE_2D: null, TEXTURE_CUBE_MAP: null, },
    { TEXTURE_2D: null, TEXTURE_CUBE_MAP: null, },
    { TEXTURE_2D: null, TEXTURE_CUBE_MAP: null, },
    { TEXTURE_2D: null, TEXTURE_CUBE_MAP: null, },
    ...
    ... up to gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) ....
  ]
};

When you call gl.activeTexture(textureUnit) what happens inside WebGL is effectively

gl.activeTexture = function(textureUnit) {
  // convert texture unit to 0 to N index
  glState.activeTextureUnit = textureUnit - gl.TEXTURE0;
};

What happens when you call gl.bindTexture is effectively this

gl.bindTexture = function(target, texture) {
  glState.textureUnits[glState.activeTextureUnit][target] = texture;
};

Uniform samplers indirectly reference texture units. You give them the index of the texture unit you want them to get their texture from.

So, in your case this code

gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, aTexture);

gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, bTexture);

Effectively makes the glState

glState = {
  activeTextureUnit: 1,    // because the last call to activeTexture was gl.TEXTURE1
  textureUnits: [
    { TEXTURE_2D: aTexture, TEXTURE_CUBE_MAP: null, },  // <=- aTexture bound
    { TEXTURE_2D: bTexture, TEXTURE_CUBE_MAP: null, },  // <=- bTexture bound
    { TEXTURE_2D: null, TEXTURE_CUBE_MAP: null, },
    { TEXTURE_2D: null, TEXTURE_CUBE_MAP: null, },
    ...
    ... up to gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) ....
  ]
};

If you call

// Create and bind a framebuffer
var outputTexture = this.makeTexture(gl.FLOAT, null);
this.attachFrameBuffer(outputTexture);

After that, well, makeTexture binds a different texture to unit 1 (since the last call to activeTexture set activeTextureUnit to 1. Then at the end it binds null so there is no texture bound to unit 1 anymore. You then draw and get the error you saw

There is no texture bound to the unit 1.

There is no "right order". There is just the global webgl state and it's your responsibility to make sure that state is setup correctly before calling gl.draw???. You could do that any way you want. For example you could have makeTexture use a different texture unit when it's making a texture. You could also have makeTexture look up the current bound texture, make its new texture, then rebind the old texture. Or, like you found you could call it before binding the textures for drawing.

That said, your code does look a little fishy in that most WebGL apps draw many times so they usually separate resource creation code (initializtion) from rendering code (drawing). Creation code creates the shaders, programs, buffers, textures, and maybe vertex array objects and the render code uses them.

The render code will then set all the state needed to draw

for each thing to draw
  useProgram 
  bind buffers and set attributes (or use vertex array object)
  bind textures to texture units
  set uniforms for program
  draw

But the code you posted has useProgram followed my makeTexture which is a creation time thing (you wouldn't likely be creating a texture before every draw call). So as your program gets bigger you'd likely call makeTexture somewhere else at init/creation rather than draw/render time

PS: Here is a webgl state diagram you can step through and see the WebGL state change.

Upvotes: 3

Related Questions