title: webgl performance differences between firefox and chrome
body: <p>I am currently developing an image processing tool for a webapplication. I need to take to png-images of the same size and combine them pixel by pixel. So far, I have set up a prototype (very much inspired by the tutorials on webglfundamentals.org) that takes two images and just multiplies their pixels. I am using the twgl-helper library for webgl from <a href="http://twgljs.org/" rel="nofollow noreferrer">http://twgljs.org/</a> . (Which I could unfortunately not put into the fiddle).</p>
  <p>I have the following question: <strong>Can anyone explain or give hints, why Firefox 78 is so much slower in this than a recent Chrome?</strong> FF averages about 34ms per render (complete refresh and wipe cash between samples) while Chrome averages 0.27ms per render. This is two orders of magnitude of a difference which i just can't explain. I have tried webgl2, it is slightly faster for both but keeps the insane difference between the two.</p>
  <p>If I need to provide more info, pls let me know, I will be back in the office on thursday. Thank you for your support and ideas.</p>
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>function main() {
  
  
      // Get A WebGL context
      var canvas = document.getElementById("webgl");
      var gl = canvas.getContext("webgl");
      if (!gl) {
          return;
      }
  
      var canvas1 = document.getElementById("canvas1");
      var canvas2 = document.getElementById("canvas2");
  
      // setup GLSL program
      var program = twgl.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);
      gl.useProgram(program);
  
      var time0 = performance.now();
      // look up where the vertex data needs to go.
      var positionLocation = gl.getAttribLocation(program, "a_position");
      var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
  
      // provide texture coordinates for the rectangle.
      var texCoordBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
  
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
          0.0, 0.0,
          1.0, 0.0,
          0.0, 1.0,
          0.0, 1.0,
          1.0, 0.0,
          1.0, 1.0]), gl.STATIC_DRAW);
  
      // vertex attributes need to be turned on explicitly
      gl.enableVertexAttribArray(texCoordLocation);
      gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
  
      // lookup uniforms
      var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
  
      // set the resolution
      gl.uniform2f(resolutionLocation, canvas1.width, canvas1.height);
  
      // Create a buffer for the position of the rectangle corners.
      var buffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
      gl.enableVertexAttribArray(positionLocation);
      gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
  
      // Set a rectangle the same size as the image.
      setRectangle(gl, 0, 0, canvas.width, canvas.height);
      // setRectangle(gl, 0, 0, 1000, 1000);
  
  
      function setupTexture(canvas, textureUnit, program, uniformName) {
          var tex = gl.createTexture();
  
          updateTextureFromCanvas(tex, canvas, textureUnit);
  
          // Set the parameters so we can render any size image.
          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.LINEAR);
          // mal ausprobieren
          gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
          gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  
          var location = gl.getUniformLocation(program, uniformName);
          gl.uniform1i(location, textureUnit);
      }
  
      function updateTextureFromCanvas(tex, canvas, textureUnit) {
          gl.activeTexture(gl.TEXTURE0 + textureUnit);
          gl.bindTexture(gl.TEXTURE_2D, tex);
          gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
      }
  
      var tex1 = setupTexture(canvas1, 0, program, "u_canvas1");
      var tex2 = setupTexture(canvas2, 1, program, "u_canvas2");
  
      // Draw the rectangle.
      gl.drawArrays(gl.TRIANGLES, 0, 6);
  
      var time1 = performance.now();
      console.log("Processing image took " + (time1 - time0) + " ms.");
      document.getElementById("performance").innerHTML = "Processing image took " + (time1 - time0) + " ms.";
  }
  
  function setRectangle(gl, x, y, width, height) {
      var x1 = x;
      var x2 = x + width;
      var y1 = y;
      var y2 = y + height;
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
          x1, y1,
          x2, y1,
          x1, y2,
          x1, y2,
          x2, y1,
          x2, y2]), gl.STATIC_DRAW);
  }
  
  const WIDTH = 1600;
  const HEIGHT = 900;
  
  addNewImage = function (path, id, width, height) {
      console.log(path)
      let newElement = document.createElement("canvas");
      document.body.appendChild(newElement);
      newElement.id = id;
      let ctx = newElement.getContext("2d");
      ctx.canvas.width = width;
      ctx.canvas.height = height;
      let input = new Image();
      input.crossOrigin = "anonymous";
      input.onload = function () {
          ctx.drawImage(input, 0, 0);
      }
      input.src = path;
  }
  
  
  
  addNewImage("https://i.imgur.com/KjUybBD.png", "canvas1", WIDTH, HEIGHT);
  addNewImage("https://i.imgur.com/ZKMnXce.png", "canvas2", WIDTH, HEIGHT);</code></pre>
  <pre class="snippet-code-css lang-css prettyprint-override"><code>canvas {
      border: 2px solid black;
      display: inline-block;
      width: 100%;
  }</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;script src="twgl.js"&gt;&lt;/script&gt;
  &lt;link rel="stylesheet" type="text/css" href="style.css" /&gt;
  &lt;button onclick="main()"&gt;click&lt;/button&gt;
  
  &lt;!-- vertex shader --&gt;
  &lt;script id="2d-vertex-shader" type="x-shader/x-vertex"&gt;
  attribute vec2 a_position;
  attribute vec2 a_texCoord;
  
  uniform vec2 u_resolution;
  
  varying vec2 v_texCoord;
  
  void main() {
     // convert the rectangle from pixels to 0.0 to 1.0
     vec2 zeroToOne = a_position / u_resolution;
  
     // convert from 0-&gt;1 to 0-&gt;2
     vec2 zeroToTwo = zeroToOne * 2.0;
  
     // convert from 0-&gt;2 to -1-&gt;+1 (clipspace)
     vec2 clipSpace = zeroToTwo - 1.0;
  
     gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
  
     // pass the texCoord to the fragment shader
     // The GPU will interpolate this value between points.
     v_texCoord = a_texCoord;
  }
  &lt;/script&gt;
  &lt;!-- fragment shader --&gt;
  &lt;script id="2d-fragment-shader" type="x-shader/x-fragment"&gt;
      precision mediump float;
  
      // our 2 canvases
      uniform sampler2D u_canvas1;
      uniform sampler2D u_canvas2;
  
      // the texCoords passed in from the vertex shader.
      // note: we're only using 1 set of texCoords which means
      //   we're assuming the canvases are the same size.
      varying vec2 v_texCoord;
  
      void main() {
           // Look up a pixel from first canvas
           vec4 color1 = texture2D(u_canvas1, v_texCoord);
  
           // Look up a pixel from second canvas
           vec4 color2 = texture2D(u_canvas2, v_texCoord);
  
           // return the 2 colors multiplied
           gl_FragColor = color1 * color2;
      }
  &lt;/script&gt;
  
  &lt;!-- &lt;canvas id="canvas1"&gt;&lt;/canvas&gt;
  &lt;canvas id="canvas2"&gt;&lt;/canvas&gt; --&gt;
  &lt;div id="performance"&gt;&lt;/div&gt;
  &lt;canvas id="webgl" width="1600" height="900"&gt;&lt;/canvas&gt;
  &lt;script src="https://twgljs.org/dist/4.x/twgl-full.min.js"&gt;&lt;/script&gt;</code></pre>
  </div>
  </div>
  </p>