const vert = `#version 300 es
layout(location = 0) in vec3 position;
out vec2 uv;
void main(void) {  uv = position.xy * 0.5 + 0.5;
  gl_Position = vec4(position, 1.0);
}
`;
 
const frag1 = `#version 300 es
precision highp float;
uniform float time;
uniform float dx;
uniform float dy;
 
float w2 = 1.0 / float(${innerWidth * 2});float w10 = 1.0 / (float(${innerWidth * 2}) * 100.); float convert = 3.141592653589 / 180.;
 
float sn;
float cs;
 
out vec4 fragColor;
 
void main(void) {  vec2 uv = gl_FragCoord.xy / vec2(float(${innerWidth * 2}), float(${innerHeight * 2})); 
  float xp = uv.x * float(${innerWidth * 2});  float yp = (1. - uv.y) * float(${innerHeight * 2});  int x = 10 | 2;
  int xp2 = int(xp) << 1; 
  float t = mod(float(int(yp) | int(xp + yp * .1) ) * (xp + dx) * (w10), 6.283185307179586);
 
  if (t < 0.) {    sn = 1.27323954 * t + .405284735 * t * t; 
  } else {    sn = 1.27323954 * t - 0.405284735 * t * t;
  }
 
  t = mod(float( (xp2 | int(yp + xp * .1 + dy) )) * convert, 6.283185307179586);
 
  t += 1.57079632;
  if (t > 3.14159265) {    t -= 6.28318531;
  }
  if (t < 0.) {    cs = 1.27323954 * t + 0.405284735 * t * t;
  } else {    cs = 1.27323954 * t - 0.405284735 * t * t;
  }
 
  float c1 = 30. * (sn - cs);
 
  if (c1 < 0.) c1 = 255. - c1;
  c1 = float((int(c1) << 4 | int(c1)) & 255) / 255.;
 
  fragColor = vec4(c1, c1, c1, 1.0);
} 
`;
 
const frag2 = `#version 300 es
precision highp float;
in vec2 uv;
 
  // The max kernel size (impacts performance)
#define maxKernel 1.0
 
// The max offset (doesn't impact performance)
#define maxOffset 34.0
uniform sampler2D uTexture; 
 
 
// https://www.shadertoy.com/view/mdGcRh
vec3 fastBlur(vec2 uv, vec2 texel, vec2 kd)
{float r = kd.x * kd.y;
float rr = 1.0/r;
vec3 col = vec3(0.0);
float a = 1.0;
 
for (float x = -r; x <= r; x += kd.y) {       for (float y = -r; y <= r; y += kd.y)  {a++;
vec2 c = uv + vec2(x,y) * texel;
col += texture(uTexture, c + fract(sin(dot(c, vec2(12.9898, 78.233))) * 43758.5453) * texel * kd.y * 2.0 - kd.yy * texel).rgb * (2.0 - distance(vec2(x,y) * rr, vec2(0.0))); 
}
}
 
return col / a;
}
 
 
out vec4 fragColor;
void main(void) {  vec4 color = texture(uTexture, uv);
  vec4 colorB = texture(uTexture, vec2(uv.x, 1. - uv.y));
  vec3 result = (color.rgb - colorB.rgb) * 1.2;
 
    float amt = .4;
    vec2 texel = vec2(1.0)/ vec2(float(${innerWidth * 2}), float(${innerHeight * 2})); 
    vec2 kd = round(vec2(amt * maxKernel + 1.0, amt * maxOffset + 1.0));
 
    vec3 blur = fastBlur(uv, texel, kd);
 
  fragColor = vec4(result, color.a);
}
`;
 
  const frag3 = `#version 300 es
precision highp float;
in vec2 uv;
 
  // The max kernel size (impacts performance)
#define maxKernel 4.0
 
// The max offset (doesn't impact performance)
#define maxOffset 34.0
uniform sampler2D uTexture; 
 
 
// https://www.shadertoy.com/view/mdGcRh
vec3 fastBlur(vec2 uv, vec2 texel, vec2 kd)
{float r = kd.x * kd.y;
float rr = 1.0/r;
vec3 col = vec3(0.0);
float a = 1.0;
 
for (float x = -r; x <= r; x += kd.y) {       for (float y = -r; y <= r; y += kd.y)  {a++;
      vec2 c = uv + vec2(x,y) * texel;
 
      col += texture(
 
      uTexture, 
 
      c 
      + fract(sin(dot(c, vec2(2.9898, 78.233))) * 13758.5453) * texel * kd.y * 2.4
      - kd.yy * texel
 
      ).rgb * (2.0 - distance(vec2(x,y) * rr, vec2(0.0)));
}
}
 
return col / a;
}
 
 
out vec4 fragColor;
void main(void) {  vec4 color = texture(uTexture, uv);
 
    float amt = .4;
    vec2 texel = vec2(1.0)/ vec2(float(${innerWidth * 2}), float(${innerHeight * 2})); 
    vec2 kd = round(vec2(amt * maxKernel + 1.0, amt * maxOffset + 1.0));
 
  vec4 blur = vec4(fastBlur(uv, texel, kd), 1.);
    //1-(1-A)*(1-B)
  vec3 f = 1. - (1. - color.rgb) * (1. - blur.rgb);  
 
 
  fragColor = vec4(f.rgb, 1.);
} 
`;
 
 
 
document.body.style.background = '#000';
const gl = document.body
  .appendChild(document.createElement('canvas'))  .getContext('webgl2'); 
Object.assign(gl.canvas.style, {  position: 'absolute',
  left: 0,
  top: 0
});
 
const s = 1;
const verts = new Float32Array([
  -s, -s, 0,
  s, -s, 0,
  -s, s, 0,
  s, s, 0,
]);
 
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, verts, gl.STATIC_DRAW);
 
function createShaderProgram(vertexSource, fragmentSource) {  const vs = gl.createShader(gl.VERTEX_SHADER);
  gl.shaderSource(vs, vertexSource);
  gl.compileShader(vs);
 
  const fs = gl.createShader(gl.FRAGMENT_SHADER);
  gl.shaderSource(fs, fragmentSource);
  gl.compileShader(fs);
 
  const program = gl.createProgram();
  gl.attachShader(program, vs);
  gl.attachShader(program, fs);
  gl.linkProgram(program);
 
  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {    console.error('Could not link program:', gl.getProgramInfoLog(program));  }
 
  return program;
}
 
const program1 = createShaderProgram(vert, frag1);
const program2 = createShaderProgram(vert, frag2);
const program3 = createShaderProgram(vert, frag3);
 
const framebuffer1 = gl.createFramebuffer();
const framebuffer2 = gl.createFramebuffer();
const texture1 = gl.createTexture();
const texture2 = gl.createTexture();
 
gl.bindTexture(gl.TEXTURE_2D, texture1);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, innerWidth, innerHeight, 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);
 
gl.bindTexture(gl.TEXTURE_2D, texture2);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, innerWidth, innerHeight, 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);
 
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer1);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture1, 0);
 
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer2);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture2, 0);
 
const onResize = () => {  gl.canvas.width = innerWidth * 2;
  gl.canvas.height = innerHeight * 2;
  gl.canvas.style.width = innerWidth + 'px'
  gl.canvas.style.height = innerHeight + 'px'
  gl.viewport(0, 0, innerWidth * 2, innerHeight * 2);
  gl.bindTexture(gl.TEXTURE_2D, texture1);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, innerWidth * 2, innerHeight * 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
  gl.bindTexture(gl.TEXTURE_2D, texture2);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, innerWidth * 2, innerHeight * 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
};
 
window.onresize = onResize;
onResize();
 
gl.disable(gl.DEPTH_TEST);
 
let mx = 0;
let my = 0;
let dx = 0;
let dy = 0;
 
window.onpointermove = e => {  mx = e.clientX;
  my = e.clientY;
};
 
const loop = () => { 
  dx += (mx * 2 - (innerWidth) - dx) / 8;
  dy += (my * 2 - dy) / 8;
 
  // first pass
  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer1);
  gl.useProgram(program1);
 
  const mxUniform = gl.getUniformLocation(program1, 'dx');
  gl.uniform1f(mxUniform, dx);
 
  const myUniform = gl.getUniformLocation(program1, 'dy');
  gl.uniform1f(myUniform, dy);
 
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  const pos1 = gl.getAttribLocation(program1, 'position');
  gl.enableVertexAttribArray(pos1);
  gl.vertexAttribPointer(pos1, 3, gl.FLOAT, false, 0, 0);
  gl.clearColor(0, 0, 0, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
 
  // second pass
  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer2);
  gl.useProgram(program2);
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  const pos2 = gl.getAttribLocation(program2, 'position');
  gl.enableVertexAttribArray(pos2);
  gl.vertexAttribPointer(pos2, 3, gl.FLOAT, false, 0, 0);
  const uTexture = gl.getUniformLocation(program2, 'uTexture');
  gl.uniform1i(uTexture, 0);
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, texture1);
  gl.clearColor(0, 0, 0, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
 
  // third pass
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.useProgram(program3);
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  const pos3 = gl.getAttribLocation(program3, 'position');
  gl.enableVertexAttribArray(pos3);
  gl.vertexAttribPointer(pos3, 3, gl.FLOAT, false, 0, 0);
  const u2Texture = gl.getUniformLocation(program3, 'uTexture');
  gl.uniform1i(u2Texture, 0);
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, texture2);
  gl.clearColor(0, 0, 0, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 
 
  requestAnimationFrame(loop);
};
loop();