Histogram in WebGL2

HTML

<script id="hist-vs" type="not-js">
#version 300 es
in float pixelId;

uniform sampler2D u_texture;
uniform vec4 u_colorMult;

void main() {
  // based on an id (0, 1, 2, 3 ...) compute the pixel x, y for the source image
  ivec2 resolution = textureSize(u_texture, 0);
  ivec2 pixel = ivec2(gl_VertexID % resolution.x, gl_VertexID / resolution.x);

  // get the pixels but 0 out channels we don't want
  vec4 color = texelFetch(u_texture, pixel, 0) * u_colorMult;

  // add up all the channels. Since 3 are zeroed out we'll get just one channel
  float colorSum = color.r + color.g + color.b + color.a;

  // set the position to be over a single pixel in the 256x1 destination texture
  gl_Position = vec4((colorSum * 255.0 + 0.5) / 256.0 * 2.0 - 1.0, 0.5, 0, 1);

  gl_PointSize = 1.0;
}
</script>
<script id="hist-fs" type="not-js">
#version 300 es
precision mediump float;
out vec4 outColor;
void main() {
  outColor = vec4(1);
}
</script>
<script id="max-fs" type="not-js">
#version 300 es
precision mediump float;

uniform sampler2D u_texture;

out vec4 outColor;

void main() {
  vec4 maxColor = vec4(0);

  // we know the texture is 256x1 so just go over the whole thing
  for (int i = 0; i < 256; ++i) {
    // get max value of pixel
    maxColor = max(maxColor, texelFetch(u_texture, ivec2(i, 0), 0));
  }

  outColor = maxColor;
}
</script>
<script id="show-vs" type="not-js">
#version 300 es
in vec4 position;
void main() {
  gl_Position = position;
}
</script>
<script id="show-fs" type="not-js">
#version 300 es
precision mediump float;

uniform sampler2D u_histTexture;
uniform vec2 u_resolution;
uniform sampler2D u_maxTexture;

out vec4 outColor;

void main() {
  // get the max color constants
  vec4 maxColor = texture(u_maxTexture, vec2(0));

  // compute our current UV position
  vec2 uv = gl_FragCoord.xy / u_resolution;

  // Get the history for this color
  // (note: since u_histTexture is 256x1 uv.y is irrelevant
  vec4 hist = texture(u_histTexture, uv);

  // scale by...

CSS

img, canvas { border: 1px solid black; margin: 5px; }

JavaScript

"use strict";

var canvas = document.createElement("canvas");
canvas.width = 256;
canvas.height = 120;
var m4 = twgl.m4;
var gl = canvas.getContext("webgl2");
var ext = gl.getExtension("EXT_color_buffer_float");
if (!ext) {
   alert("requires EXT_color_buffer_float");
}

twgl.createTexture(gl, {
  //src: "https://i.imgur.com/9Y3sd8S.png",
  src: "https://farm1.staticflickr.com/293/18414763798_cb8ebded43_m_d.jpg",
  // required link: https://www.flickr.com/photos/greggman/18414763798/in/album-72157653822314919/
  min: gl.NEAREST,
  mag: gl.NEAREST,
  wrap: gl.CLAMP_TO_EDGE,
  crossOrigin: "",
}, function(err, tex, img) {
  log("img");
  document.body.appendChild(img);
  log("histogram");
  document.body.appendChild(canvas);

  var quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
  var histProgramInfo = twgl.createProgramInfo(gl, ["hist-vs", "hist-fs"]);
  var numPixels = img.width * img.height;

  // make a 256x1 RGBA floating point texture and attach to a framebuffer
  var sumFbi = twgl.createFramebufferInfo(gl, [
    { internalFormat: gl.RGBA32F,
      min: gl.NEAREST,
      mag: gl.NEAREST,
      wrap: gl.CLAMP_TO_EDGE,
    },
  ], 256, 1);

  // Render sum of each color

  // we're going to render a gl.POINT for each pixel in the source image
  // That point will be positioned based on the color of the source image
  // we're just going to render vec4(1,1,1,1). This blend function will
  // mean each time we render to a specific point that point will get
  // incremented by 1.
  gl.blendFunc(gl.ONE, gl.ONE);
  gl.enable(gl.BLEND);
  gl.useProgram(histProgramInfo.program);
  twgl.bindFramebufferInfo(gl, sumFbi);
  // render each channel separately since we can only position each POINT
  // for one channel at a time.
  for (var channel = 0; channel < 4; ++channel) {
    gl.colorMask(channel === 0, channel === 1, channel === 2, channel === 3);
    twgl.setUniforms(histProgramInfo, {
      u_texture: tex,
      u_colorMult: [
        channel === 0 ? 1...