title: How to work with framebuffers in webgl?
body: <p>I have been trying to understand framebuffer in WebGL/OpenGL-ES.
  I know that we can blend multiple textures using framebuffer.</p>
  
  <p>So, to understand that I wrote a sample by taking a 1*1 texture and tried to apply framebuffer logic on top of it.
  But , it didn't work.</p>
  
  <p>See snippet at bottom, if you click on "mix red and blue", the images doesn't get rendered, am I doing anything wrong?</p>
  
  <p>Code :
  `</p>
  
  <pre><code>var canvas, gl, attrPosition, texture, program, vertexBuffer, textureBuffer, vertices, texVertices, attrPos, attrTexPos, textures = [], framebuffers = [];
  
  canvas = document.getElementById('canvas');
      gl = getWebGL();
      vertices = new Float32Array([
          -1.0, -1.0,  
           1.0, -1.0, 
           1.0,  1.0, 
          -1.0,  1.0, 
          -1.0, -1.0, 
      ]);
  
      texVertices = new Float32Array([
          0.0, 0.0,
          1.0, 0.0,
          1.0, 1.0,
          0.0, 1.0,
          0.0, 0.0
      ]);
      var getProgram = function () {
          var vs = createVertexShader([
              'attribute vec2 attrPos;',
              'attribute vec2 attrTexPos;',
              'varying highp vec2 vTexCoord;',
              'void main() {',
                  '\tgl_Position = vec4(attrPos, 0.0, 1.0);',
              '}'
          ].join('\n'));
  
          var fs = createFragmentShader([
              'varying highp vec2 vTexCoord;',
              'uniform sampler2D uImage;',
              'void main() {',
                  '\tgl_FragColor = texture2D(uImage, vTexCoord);',
              '}'
          ].join('\n'));
          return createAndLinkPrograms(vs, fs);
      };
  
      var render = function () {
          gl.clear(gl.DEPTH_BUFFER_BIT|gl.COLOR_BUFFER_BIT);
          gl.bindTexture(gl.TEXTURE_2D, texture);
          gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
          gl.vertexAttribPointer(attrPos, 2, gl.FLOAT, gl.FALSE, 0, 0);
          gl.bindBuffer(gl.ARRAY_BUFFER, textureBuffer);
          gl.vertexAttribPointer(attrTexPos, 2, gl.FLOAT, gl.FALSE, 0, 0);
          gl.drawArrays(gl.TRIANGLE_STRIP, 0, 5);
      };
      if (gl) {
          gl.clearColor(0.1, 0.5, 1.0, 1.0);
          render();
          program = getProgram();
          texture = createAndSetupTexture();
          vertexBuffer = createAndBindBuffer(vertices, gl.ARRAY_BUFFER);
          attrPos = gl.getUniformLocation(program, 'attrPos');
          gl.enableVertexAttribArray(attrPos);
  
          textureBuffer = createAndBindBuffer(texVertices, gl.ARRAY_BUFFER);
          attrTexPos = gl.getUniformLocation(program, 'attrTexPos');
          gl.enableVertexAttribArray(attrTexPos);
  
          gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([123, 0, 60, 255]));
          render();
      }
  
      var initPingPongTextures = function(textures, framebuffers) {
          for (var i = 0; i &lt; 2; ++i) {
              var tex = createAndSetupTexture(gl);
              textures.push(tex);
              // make the texture the same size as the image
              gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
              // Create a framebuffer
              var fbo = gl.createFramebuffer();
              framebuffers.push(fbo);
              gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
              // Attach a texture to it.
              gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
          }
      }
  
      var setFramebuffer = function(fbo, width, height) {
          gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
          gl.viewport(0, 0, width, height);
      };
  
      var mixRedAndBlue = function () {
          gl.activeTexture(gl.TEXTURE0);
          gl.bindTexture(gl.TEXTURE_2D, texture);
  
          setFramebuffer(framebuffers[0], 1, 1);
          gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255]));
          render();
          gl.bindTexture(gl.TEXTURE_2D, textures[0]);
  
          setFramebuffer(framebuffers[1], 1, 1);
          gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 255, 0, 255]));
          render();
          gl.bindTexture(gl.TEXTURE_2D, textures[1]);
  
          setFramebuffer(null, 1, 1);
          render();
      };`
  </code></pre>
  
  <p><div class="snippet" data-lang="js" data-hide="true" data-console="false" data-babel="false">
  <div class="snippet-code snippet-currently-hidden">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>var getWebGLContext = function(canvas) {
   var webglContextParams = ['webgl', 'experimental-webgl', 'webkit-3d', 'moz-webgl'];
   var webglContext = null;
   for (var index = 0; index &lt; webglContextParams.length; index++) {
    try {
     webglContext = canvas.getContext(webglContextParams[index]);
     if(webglContext) {
      //breaking as we got our context
      break;
     }
    } catch (E) {
     console.log(E);
    }
   }
   if(webglContext === null) {
    alert('WebGL is not supported on your browser.');
   } else {
    //WebGL is supported in your browser, lets render the texture
   }
   fillGLForCleanUp(webglContext);
   return webglContext;
  }
  var createVertexShader = function (vertexShaderSource) {
   console.log(vertexShaderSource);
   var vertexShader = gl.createShader(gl.VERTEX_SHADER);
   gl.shaderSource(vertexShader, vertexShaderSource);
   gl.compileShader(vertexShader);
   return vertexShader;
  }
  
  var createFragmentShader = function (fragmentShaderSource) {
   console.log(fragmentShaderSource);
   var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
   gl.shaderSource(fragmentShader, fragmentShaderSource);
   gl.compileShader(fragmentShader);
   return fragmentShader;
  }
  
  
  var createAndLinkPrograms = function (vertexShader, fragmentShader) {
   var program = gl.createProgram();
   gl.attachShader(program, vertexShader);
   gl.attachShader(program, fragmentShader);
   gl.linkProgram(program);
      if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
          alert('Could not initialise shaders');
      }
   gl.useProgram(program);
   return program;
  }
  
  var createAndBindBuffer = function (verticesOrIndices, bufferType) {
   var buffer = gl.createBuffer();
   gl.bindBuffer(bufferType, buffer);
   gl.bufferData(bufferType, verticesOrIndices, gl.STATIC_DRAW);
   //clear memory
   gl.bindBuffer(bufferType, null);
   return buffer;
  }
  
  var allowAllImageSizes = function() {
     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);
     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    // gl.bindTexture(gl.TEXTURE_2D, null);
  } 
  
  var createAndSetupTexture = function() {
   var texture = gl.createTexture();
   gl.bindTexture(gl.TEXTURE_2D, texture);
   allowAllImageSizes();
   gl.textures.push(texture);
   return texture;
  }
  
  var getWebGL = function (canvas, width, height) {
   if(!canvas) {
    canvas = document.createElement('canvas');
    canvas.id = 'canvas';
    canvas.width = !width ? 512 : width;
    canvas.height = !height ? 512 : height;
    document.body.appendChild(canvas);
   } else {
    canvas.width = !width ? 512 : width;
    canvas.height = !height ? 512 : height;
   }
   return getWebGLContext(canvas);
  }
  
  var fillGLForCleanUp = function (gl) {
   gl.textures = [];
   gl.framebuffers = [];
   gl.array_buffer = [];
   gl.element_array_buffers = [];
  }
  
  
  var canvas, gl, attrPosition, texture, program, 
  vertexBuffer, textureBuffer, vertices, texVertices,
  attrPos, attrTexPos, textures = [], framebuffers = [];
  canvas = document.getElementById('canvas');
  gl = getWebGL(canvas);
  vertices = new Float32Array([
   -1.0, -1.0,  
    1.0, -1.0, 
    1.0,  1.0, 
   -1.0,  1.0, 
   -1.0, -1.0, 
  ]);
  
  texVertices = new Float32Array([
   0.0, 0.0,
   1.0, 0.0,
   1.0, 1.0,
   0.0, 1.0,
   0.0, 0.0
  ]);
  var getProgram = function () {
   var vs = createVertexShader([
    'attribute vec2 attrPos;',
    'attribute vec2 attrTexPos;',
    'varying highp vec2 vTexCoord;',
    'void main() {',
     '\tgl_Position = vec4(attrPos, 0.0, 1.0);',
    '}'
   ].join('\n'));
  
   var fs = createFragmentShader([
    'varying highp vec2 vTexCoord;',
    'uniform sampler2D uImage;',
    'void main() {',
     '\tgl_FragColor = texture2D(uImage, vTexCoord);',
    '}'
   ].join('\n'));
   return createAndLinkPrograms(vs, fs);
  };
  
  var render = function () {
   gl.clear(gl.DEPTH_BUFFER_BIT|gl.COLOR_BUFFER_BIT);
   gl.bindTexture(gl.TEXTURE_2D, texture);
   gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
   gl.vertexAttribPointer(attrPos, 2, gl.FLOAT, gl.FALSE, 0, 0);
   gl.bindBuffer(gl.ARRAY_BUFFER, textureBuffer);
   gl.vertexAttribPointer(attrTexPos, 2, gl.FLOAT, gl.FALSE, 0, 0);
   gl.drawArrays(gl.TRIANGLE_STRIP, 0, 5);
  };
  if (gl) {
   gl.clearColor(0.1, 0.5, 1.0, 1.0);
   render();
   program = getProgram();
   texture = createAndSetupTexture();
   vertexBuffer = createAndBindBuffer(vertices, gl.ARRAY_BUFFER);
   attrPos = gl.getUniformLocation(program, 'attrPos');
   gl.enableVertexAttribArray(attrPos);
  
   textureBuffer = createAndBindBuffer(texVertices, gl.ARRAY_BUFFER);
   attrTexPos = gl.getUniformLocation(program, 'attrTexPos');
   gl.enableVertexAttribArray(attrTexPos);
  
   gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([123, 0, 60, 255]));
   render();
  }
  
  var initPingPongTextures = function(textures, framebuffers) {
   for (var i = 0; i &lt; 2; ++i) {
    var tex = createAndSetupTexture(gl);
    textures.push(tex);
    // make the texture the same size as the image
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
    // Create a framebuffer
    var fbo = gl.createFramebuffer();
    framebuffers.push(fbo);
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
    // Attach a texture to it.
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
   }
  }
  
  var setFramebuffer = function(fbo, width, height) {
   gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
   gl.viewport(0, 0, width, height);
  };
  
  var mixRedAndBlue = function () {
   gl.activeTexture(gl.TEXTURE0);
   gl.bindTexture(gl.TEXTURE_2D, texture);
  
   setFramebuffer(framebuffers[0], 1, 1);
   gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255]));
   render();
   gl.bindTexture(gl.TEXTURE_2D, textures[0]);
  
   setFramebuffer(framebuffers[1], 1, 1);
   gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 255, 0, 255]));
   render();
   gl.bindTexture(gl.TEXTURE_2D, textures[1]);
  
   setFramebuffer(null, 1, 1);
   render();
  };</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;button id="redImg" onclick="mixRedAndBlue()"&gt;Mix Red and blue&lt;/button&gt;&lt;hr/&gt;
  &lt;canvas id="canvas" width=512 height=512&gt;&lt;/canvas&gt;</code></pre>
  </div>
  </div>
  </p>
  
  <p><strong>Edit 1 :</strong></p>
  
  <p>I am trying to achieve the same for multiple programs with multiple fragment shaders because having if/else statements within the fragment shader is not recommended as it runs for each pixel.</p>
  
  <p>`<br>
  Shaders.prototype.VS_Base = [
              'attribute vec3 verticesPosition;',
              'attribute vec2 texturePosition;',
              'varying highp vec2 vTextureCoord;',
              'void main(void) {',
              '\tgl_Position = vec4(verticesPosition * vec3(1.0, -1.0, 1.0), 0.5);',
              '\tvTextureCoord = texturePosition;',
              '}'
      ].join('\n');</p>
  
  <pre><code>Shaders.prototype.FS_Base_Image_RED = [
          '#ifdef GL_ES',
          'precision highp float;',
          '#endif',
          'uniform sampler2D uImage;',
          'varying highp vec2 vTextureCoord;',
          'void main (void) {',
          '\tgl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);//texture2D(uImage, vTextureCoord);',
          '}'
  ].join('\n');
  
  Shaders.prototype.FS_Base_Image_BLUE = [
          '#ifdef GL_ES',
          'precision highp float;',
          '#endif',
          'uniform sampler2D uImage;',
          'varying highp vec2 vTextureCoord;',
          'void main (void) {',
          '\tgl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);//texture2D(uImage, vTextureCoord);',
          '}'
  ].join('\n');`
  </code></pre>
  
  <p>Now I have 2 separate programs for both the fragment shader and I need to use framebuffers for mixing Red and Blue. I am not looking for <code>mix()</code> as the actual scenario is very complex and that's the reason I am using multiple programs with fragment shaders for avoiding conditional if/else statements.</p>