1

I have a canvas on which I render a given image by applying some WebGL filter on it. The canvas that is displayed must be reused. That is I keep getting different images (other part of the program) which I should draw on this canvas with the same filter (fragment shader) being applied.

I created a function drawoncanvas(gl, img, img.width, img.height) here the gl is the webglrenderingcontext of the canvas , img is the html element with image to be used. The function has all the WebGL processing part. So, whenever I get a new image to be processed and display on canvas. I call this function with the new img element and webglrenderingcontext of the same canvas.

The issue I am facing is that I can see the previously painted content on canvas behind the current content (wherever the current content is transparent). And if I pass same image twice the canvas shows the content upside down.

I want to know how can I clear the canvas and/or the WebGL rendering context before I start using the new image. So that it doesn't show the the old stuff beneath or give these issues.

EDIT: My code snippet is as follows

/* img1, img2 are img elements I get from other part of the program according to user selections. 
As per user input more than 2 images can also be received. Demonstrating issue using two */
const canvas = document.getElementB("canvas"); //the canvas on which I am rendering.
const gl = canvas.getContext("webgl");
drawfilter(gl,img1,img1.width, img1.height); // first displaying image one
drawfilter(gl,img2,img2.width, img2.height); // when second image is received the function is called again
    
function drawfilter(gl,img,width,height){     
    gl.clearColor(1, 1, 1, 1);
    gl.clear(gl.COLOR_BUFFER_BIT||gl.DEPTH_BUFFER_BIT||gl.STENCIL_BUFFER_BIT);
    function createShader(gl, type, shaderSource) {
        const shader = gl.createShader(type);
        gl.shaderSource(shader, shaderSource);
        gl.compileShader(shader);
    
        const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
            if (!success) {
                console.warn(gl.getShaderInfoLog(shader));
                gl.deleteShader(shader);
            }
    
       return shader;
    }
    
    //the shaderssources cannot be displayed here
    const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);  //simple vertex shader
    const fragmentShaderA = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSourceA);//simple fragment shader
    const fragmentShaderB = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSourceB);//simple fragment shader
    /* this shader takes two texture inputs. 1- original image, 
    2- ShadersourceA applied on original image then on output shadersouceB is applied and the result is passed as second texture to this fragment shader */
    const fragmentShaderC = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSourceC);
    
    function createProgram(gl, vertexShader, fragmentShader) {
        const program = gl.createProgram();
        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);
        gl.linkProgram(program);
    
        const success = gl.getProgramParameter(program, gl.LINK_STATUS);
            if (!success) {
                console.log(gl.getProgramInfoLog(program));
                gl.deleteProgram(program);
            }
        return program;
    }
    
    const programA = createProgram(gl, vertexShader, fragmentShaderA);
    const programB = createProgram(gl, vertexShader, fragmentShaderB);
    const programC = createProgram(gl, vertexShader, fragmentShaderC);
    const texFbPair1 = createTextureAndFramebuffer(gl);
    const texFbPair2 = createTextureAndFramebuffer(gl);
    
    function setAttributes(program) {
        const positionLocation = gl.getAttribLocation(program, 'position');
        const positionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
            -1, -1, -1, 1, 1, -1,
            1, 1, 1, -1, -1, 1,
        ]), gl.STATIC_DRAW);
        gl.enableVertexAttribArray(positionLocation);
        gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
        const texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
        const texCoordBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
            0.0, 1.0,
            0.0, 0.0,
            1.0, 1.0,
            1.0, 0.0,
            1.0, 1.0,
            0.0, 0.0]), gl.STATIC_DRAW);
        gl.enableVertexAttribArray(texCoordLocation);
        gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
   }
    
   const texture = gl.createTexture();
   texture.image = new Image();
   texture.image.onload = function () {
       handleLoadedTexture(gl, texture);
   };
   texture.image.crossOrigin = '';
   texture.image.src = img.getAttribute('src');
   function handleLoadedTexture(gl, texture, callback) {
        gl.bindTexture(gl.TEXTURE_2D, texture);
        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_MAG_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image);
    
        setAttributes(programA);
        gl.useProgram(programA);
        gl.bindFramebuffer(gl.FRAMEBUFFER, texFbPair1.fb);
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.clearColor(0, 0, 1, 1);
        gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
        gl.drawArrays(gl.TRIANGLES, 0, 6);
    
        setAttributes(programB);
        gl.useProgram(programB);
        gl.bindFramebuffer(gl.FRAMEBUFFER, texFbPair2.fb);
        gl.bindTexture(gl.TEXTURE_2D, texFbPair1.tex);
        gl.clearColor(0, 0, 0, 1);
        gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
        gl.drawArrays(gl.TRIANGLES, 0, 6)
    
        setAttributes(programC);
        gl.useProgram(programC);
        var uTextureLocation = gl.getUniformLocation(programC, "uTexture");
        var originalTextureLocation = gl.getUniformLocation(programC, "originalTexture");
        // set which texture units to render with.
        gl.uniform1i(uTextureLocation, 0);  // texture unit 0
        gl.uniform1i(originalTextureLocation, 1);  // texture unit 1
        // Set each texture unit to use a particular texture.
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, texFbPair2.tex);
        gl.activeTexture(gl.TEXTURE1);
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        gl.clearColor(0, 0, 0, 1);
        gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
        gl.drawArrays(gl.TRIANGLES, 0, 6)
    }
    function createTextureAndFramebuffer(gl) {
        const tex = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, tex);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        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);
        const fb = gl.createFramebuffer();
        gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
        return { tex: tex, fb: fb };
    }
}

ASR
  • 33
  • 6
  • Does this answer your question? [How to clear the canvas for redrawing](https://stackoverflow.com/questions/2142535/how-to-clear-the-canvas-for-redrawing) – blex Jul 08 '20 at 10:39
  • I am unable to get 2d context and use clearrect. canvas.getContext(“2d”) gives null – ASR Jul 08 '20 at 10:41
  • 1
    What if you try `var gl = canvas.getContext("webgl"); gl.clearColor(1, 1, 1, 1); gl.clear(gl.COLOR_BUFFER_BIT);`? [Some MDN Docs](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/clear) – blex Jul 08 '20 at 10:48
  • 1
    @blex I already tried that but of no use – ASR Jul 08 '20 at 10:50
  • 2
    Without code **in the question itself** we can't debug your code. [WebGL clears itself by default](https://stackoverflow.com/questions/14606408/webgl-prevent-color-buffer-from-being-cleared) so you must be doing something special to prevent that. Voting to close as off topic as S.O. requires a repo if you want to debugging help. Please add a **minimal** repo in a [snippet](https://stackoverflow.blog/2014/09/16/introducing-runnable-javascript-css-and-html-code-snippets/). You can use images from [imgur](https://imgur.com/) – gman Jul 09 '20 at 00:50
  • @gman I edited my code to include code snippet . – ASR Jul 09 '20 at 07:16
  • how to reopen it to get help – ASR Jul 09 '20 at 07:17
  • I voted to reopen. Looks to me like you need to call `gl.clear(gl.COLOR_BUFFER_BIT)` after every call to `gl.clearColor`. As it is you have 4 calls to `gl.clearColor` but only 1 call to `gl.clear` – gman Jul 09 '20 at 07:28
  • No that didn’t work – ASR Jul 09 '20 at 08:40
  • Then you're on your own. You're clearly doing something else not shown in your code. Post working code or we can't help you. – gman Jul 09 '20 at 08:47
  • What I could figure out by using console.log() debugging is it is initially calling image one to load and by the time its loaded the second function is called. The second function loads its image applies filter and draws it on the canvas. Then the first image is loaded drawn behind the second image on the canvas without any filters applied to it. Also at this time the second image is flipped up side down on canvas – ASR Jul 09 '20 at 10:14

1 Answers1

1

Please next time post a working sample so we don't have to spend the time doing it ourselves. You can load images from imgur.

The issue is the first time you call handleLoadedTexture at the bottom it sets the active texture unit to 1 with gl.activeTexture(gl.TEXTURE1) which means the second time handleLoadedTexture is called it's binding the texture to texture unit 1 where as the first 2 shaders are using texture unit 0 which still has the texture from the first time handleLoadedTexture was called bound to it.

Otherwise, other issues with the code

  • I had to wait for img1 and img2 to load otherwise I can't read img.width and img.height

    Now maybe in your actual code they are already loaded but if they are already loaded then there's no reason to load them again

  • The code is compiling all 3 shaders, once for each call to drawfilter but arguably it should compile them once at init time and use the same shaders on all calls to drawFilter

  • It code is creating new buffers for every draw call. You only need one set of buffers which again should happen at init time. Setting the attributes needs to happen before each draw call, creating the buffers and putting data in them does not.

    Well, technically, setting the attributes only needs to happen if they need to be different. If you force the position and a_texCoord attributes to be at the same locations with bindAttribLocation before calling linkProgram so that they match locations across programs then you'd only need to set the attributes once assuming you also use the same buffers with the same data (see previous point)

  • || (logical or) is not the same as | (binary or). For gl.clear you need to use binary or gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT|gl.STENCIL_BUFFER_BIT otherwise the value you pass to gl.clear will be wrong and your canvas won't be cleared. There isn't much reason to clear in this example though as blending is not on and the draw calls draw every pixel of the canvas

  • setting gl.clearColor for each framebuffer doesn't do anything unless you call gl.clear but like above, since the draw will effect every pixel and blending is off calling gl.clear would not change the results.

  • viewport settings don't match the framebuffers. The framebuffer textures are created to be the same size as the images but the viewport settings were set to the size of the canvas. They should be set the same size as the framebuffer attachments

const vertexShaderSource = `
attribute vec4 position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
  gl_Position = position;
  v_texCoord = a_texCoord;
}
`;

const fragmentShaderSourceA = `
precision mediump float;
uniform sampler2D uTexture;
varying vec2 v_texCoord;
void main() {
  gl_FragColor = texture2D(uTexture, v_texCoord);
}
`;

const fragmentShaderSourceB = `
precision mediump float;
uniform sampler2D uTexture;
varying vec2 v_texCoord;
void main() {
  gl_FragColor = texture2D(uTexture, v_texCoord.yx);
}
`;

const fragmentShaderSourceC = `
precision mediump float;
uniform sampler2D uTexture;
uniform sampler2D originalTexture;
varying vec2 v_texCoord;
void main() {
  vec4 color1 = texture2D(uTexture, v_texCoord);
  vec4 color2 = texture2D(originalTexture, v_texCoord);
  gl_FragColor = color1 * color2; //??
}
`;

function loadImage(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => { resolve(img); };
    img.onerror = reject;
    img.crossOrigin = "anonymous"; // only needed because images are on another domain
    img.src = url;
  });
}

async function main() {
  // we need to wait for the images to load otherwise
  // width and height will not be set.
  const [img1, img2] = await Promise.all([
    'https://i.imgur.com/KjUybBD.png',
    'https://i.imgur.com/v38pV.jpg',
  ].map(loadImage));

  /* img1, img2 are img elements I get from other part of the program according to user selections. 
  As per user input more than 2 images can also be received. Demonstrating issue using two */
  const canvas = document.getElementById("canvas"); //the canvas on which I am rendering.
  const gl = canvas.getContext("webgl");
  drawfilter(gl, img1, img1.width, img1.height); // first displaying image one
  drawfilter(gl, img2, img2.width, img2.height); // when second image is received the function is called again

  function drawfilter(gl, img, width, height) {
    gl.clearColor(1, 1, 1, 1);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

    function createShader(gl, type, shaderSource) {
      const shader = gl.createShader(type);
      gl.shaderSource(shader, shaderSource);
      gl.compileShader(shader);

      const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
      if (!success) {
        console.warn(gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
      }

      return shader;
    }

    //the shaderssources cannot be displayed here
    const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource); //simple vertex shader
    const fragmentShaderA = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSourceA); //simple fragment shader
    const fragmentShaderB = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSourceB); //simple fragment shader
    /* this shader takes two texture inputs. 1- original image, 
    2- ShadersourceA applied on original image then on output shadersouceB is applied and the result is passed as second texture to this fragment shader */
    const fragmentShaderC = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSourceC);

    function createProgram(gl, vertexShader, fragmentShader) {
      const program = gl.createProgram();
      gl.attachShader(program, vertexShader);
      gl.attachShader(program, fragmentShader);
      gl.linkProgram(program);

      const success = gl.getProgramParameter(program, gl.LINK_STATUS);
      if (!success) {
        console.log(gl.getProgramInfoLog(program));
        gl.deleteProgram(program);
      }
      return program;
    }

    const programA = createProgram(gl, vertexShader, fragmentShaderA);
    const programB = createProgram(gl, vertexShader, fragmentShaderB);
    const programC = createProgram(gl, vertexShader, fragmentShaderC);
    const texFbPair1 = createTextureAndFramebuffer(gl);
    const texFbPair2 = createTextureAndFramebuffer(gl);

    function setAttributes(program) {
      const positionLocation = gl.getAttribLocation(program, 'position');
      const positionBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, -1,
        1, 1, 1, -1, -1, 1,
      ]), gl.STATIC_DRAW);
      gl.enableVertexAttribArray(positionLocation);
      gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
      const texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
      const texCoordBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        0.0, 1.0,
        0.0, 0.0,
        1.0, 1.0,
        1.0, 0.0,
        1.0, 1.0,
        0.0, 0.0
      ]), gl.STATIC_DRAW);
      gl.enableVertexAttribArray(texCoordLocation);
      gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
    }

    const texture = gl.createTexture();
    texture.image = new Image();
    texture.image.onload = function() {
      handleLoadedTexture(gl, texture);
    };
    texture.image.crossOrigin = '';
    texture.image.src = img.getAttribute('src');

    function handleLoadedTexture(gl, texture, callback) {
      gl.activeTexture(gl.TEXTURE0);
      gl.bindTexture(gl.TEXTURE_2D, texture);
      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_MAG_FILTER, gl.NEAREST);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image);

      setAttributes(programA);
      gl.useProgram(programA);
      gl.bindFramebuffer(gl.FRAMEBUFFER, texFbPair1.fb);
      gl.bindTexture(gl.TEXTURE_2D, texture);
      gl.clearColor(0, 0, 1, 1);
      gl.viewport(0, 0, width, height);
      gl.drawArrays(gl.TRIANGLES, 0, 6);

      setAttributes(programB);
      gl.useProgram(programB);
      gl.bindFramebuffer(gl.FRAMEBUFFER, texFbPair2.fb);
      gl.bindTexture(gl.TEXTURE_2D, texFbPair1.tex);
      gl.clearColor(0, 0, 0, 1);
      gl.viewport(0, 0, width, height);
      gl.drawArrays(gl.TRIANGLES, 0, 6)

      setAttributes(programC);
      gl.useProgram(programC);
      var uTextureLocation = gl.getUniformLocation(programC, "uTexture");
      var originalTextureLocation = gl.getUniformLocation(programC, "originalTexture");
      // set which texture units to render with.
      gl.uniform1i(uTextureLocation, 0); // texture unit 0
      gl.uniform1i(originalTextureLocation, 1); // texture unit 1
      // Set each texture unit to use a particular texture.
      gl.activeTexture(gl.TEXTURE0);
      gl.bindTexture(gl.TEXTURE_2D, texFbPair2.tex);
      gl.activeTexture(gl.TEXTURE1);
      gl.bindTexture(gl.TEXTURE_2D, texture);
      gl.bindFramebuffer(gl.FRAMEBUFFER, null);
      gl.clearColor(0, 0, 0, 1);
      gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
      gl.drawArrays(gl.TRIANGLES, 0, 6)
    }

    function createTextureAndFramebuffer(gl) {
      const tex = gl.createTexture();
      gl.bindTexture(gl.TEXTURE_2D, tex);
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
      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);
      const fb = gl.createFramebuffer();
      gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
      gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
      return {
        tex: tex,
        fb: fb
      };
    }
  }
}

main();
canvas { border: 1px solid black; }
<canvas id="canvas"></canvas>
gman
  • 100,619
  • 31
  • 269
  • 393
  • << There isn't much reason to clear in this example though as blending is not on and the draw calls draw every pixel of the canvas>>. What do you mean by : the draw calls every pixel of the canvas ? it is "gl.drawArrays(gl.TRIANGLES, 0, 6)" and not gl.POINTS. – oceanGermanique Sep 11 '21 at 09:03