<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="icon" src="/images/favicon.gif" />
<style type="text/css">
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
}
html, body, canvas { margin: 0; border: 0; padding: 0; width: 100%; height: 100%; }
canvas { display: block; image-rendering: pixelated; background: #f00; }
#buttons { position: absolute; top: 0; left: 0; width: 100%; height: 100%; text-align: center; }
label { padding: 2px 6px; }
label, button {
font-family: sans-serif;
font-weight: bold;
color: #fff;
background: #555;
border: 1px outset #555;
font-size: 1.5em;
}
#data {
position: absolute;
width: 100%;
bottom: 0;
padding: 8px;
color: #ff0;
font-size: 2em;
font-family: monospace;
font-weight: bold;
text-align: center;
-webkit-text-stroke: .8px #000;
-moz-text-stroke: .8px #000;
-ms-text-stroke: .8px #000;
-o-text-stroke: .8px #000;
text-stroke: .8px #000;
}
</style>
<script id="scr-frag" type="x-shader/fragment">
// copy buffer to screen
precision mediump float;
uniform sampler2D buffer;
varying vec2 texel;
void main () {
vec4 c = texture2D(buffer, texel);
c *= c.a;
gl_FragColor = vec4(c.b * 0.5, c.b, c.b * 2.0, 1.0);
}
</script>
<script id="buf-frag" type="x-shader/fragment">
// ping-pong buffer rendering
precision mediump float;
uniform sampler2D buffer;
varying vec2 texel;
void main () {
vec4 tmp = texture2D(buffer, texel);
if (0.0 == tmp.a) {
vec2 c = 2.0 * texel - 1.0;
c *= 0.00016;
c.x += -0.7766729;
c.y += -0.13661091;
tmp.b += 0.0001220703125; // iteration count / 8192
vec2 z = tmp.rg;
vec2 square = z * z;
z.y = 2.0 * z.x * z.y + c.y;
z.x = square.x - square.y + c.x;
if (16.0 < square.x + square.y) {
// renormalized iteration count
tmp.b = log2((8192.0 * tmp.b - log2(log(square.x + square.y) * 0.5)) * 0.01);
tmp.a = 1.0; // pixel ready flag
}
tmp.rg = z;
}
gl_FragColor = tmp;
}
</script>
<script id="vertex" type="x-shader/vertex">
// draw a texture-mapped quad
precision mediump float;
attribute vec2 coord;
varying vec2 texel;
void main () {
texel = 0.5 * coord + 0.5;
gl_Position = vec4(coord, 0.0, 1.0);
}
</script>
<script type="text/javascript">
function compileShader (type, id) {
var shader = gl.createShader(type);
gl.shaderSource(shader, document.getElementById(id).innerHTML);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
console.log(gl.getShaderInfoLog(shader));
else console.log("successfully compiled "+id+" shader");
return shader;
}
function cachedUniform (pgm, dest, type) {
var u = gl.getUniformLocation(pgm, dest);
var types = [ gl.INT, gl.FLOAT,
gl.INT_VEC2, gl.INT_VEC3, gl.INT_VEC4,
gl.FLOAT_VEC2, gl.FLOAT_VEC3, gl.FLOAT_VEC4,
gl.FLOAT_MAT2, gl.FLOAT_MAT3, gl.FLOAT_MAT4
];
type = types.indexOf(type);
var functions = [
gl.uniform1i.bind(gl), gl.uniform1f.bind(gl),
gl.uniform2iv.bind(gl), gl.uniform3iv.bind(gl),
gl.uniform4iv.bind(gl), gl.uniform2fv.bind(gl),
gl.uniform3fv.bind(gl), gl.uniform4fv.bind(gl),
gl.uniformMatrix2fv.bind(gl),
gl.uniformMatrix3fv.bind(gl),
gl.uniformMatrix4fv.bind(gl)
];
var f = functions[type];
if (type < 2) // scalars
return function (v) { return f(u, v); }
if (type < 5) // integer arrays
return function (v) { return f(u, new Int32Array(v)); }
// float arrays
return function (v) { return f(u, new Float32Array(v)) }
}
addEventListener("load", function load (e) {
removeEventListener("load", load, false);
document.body.onselectstart = function () { return false; }
canvas = document.getElementsByTagName("canvas")[0];
gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
if (!gl)
alert("Sorry, you need WebGL for this.");
if (!gl.getExtension("OES_texture_float"))
alert("Failed to activeate OES_texture_float extension");
canvas.height = innerHeight;
canvas.width = innerWidth;
var shaders = {
buf: { pgm: gl.createProgram() },
scr: { pgm: gl.createProgram() }
};
var vertex = compileShader(gl.VERTEX_SHADER, "vertex");
gl.attachShader(shaders.buf.pgm, vertex);
var buf_frag = compileShader(gl.FRAGMENT_SHADER, "buf-frag");
gl.attachShader(shaders.buf.pgm, buf_frag);
gl.linkProgram(shaders.buf.pgm);
if (!gl.getProgramParameter(shaders.buf.pgm, gl.LINK_STATUS))
console.log(gl.getProgramInfoLog(shaders.buf.pgm));
else console.log("successfully built buffer shader");
shaders.buf.coord = gl.getAttribLocation(shaders.buf.pgm, "coord");
gl.attachShader(shaders.scr.pgm, vertex);
var scr_frag = compileShader(gl.FRAGMENT_SHADER, "scr-frag");
gl.attachShader(shaders.scr.pgm, scr_frag);
gl.linkProgram(shaders.scr.pgm);
if (!gl.getProgramParameter(shaders.scr.pgm, gl.LINK_STATUS))
console.log(gl.getProgramInfoLog(shaders.scr.pgm));
else console.log("successfully built screen shader");
shaders.scr.coord = gl.getAttribLocation(shaders.scr.pgm, "coord");
gl.deleteShader(buf_frag);
gl.deleteShader(scr_frag);
gl.deleteShader(vertex);
// create quad
var quad = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, quad);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
-1,-1,-1, 1, 1,-1, 1, 1
]), gl.STATIC_DRAW);
function makeTexture (gl, x, y, type) {
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, x, y, 0, gl.RGBA, type, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
return tex;
}
var texSize = {
x: Math.pow(2, Math.ceil(Math.log2(innerWidth))),
y: Math.pow(2, Math.ceil(Math.log2(innerHeight)))
};
console.log("texture size: " + texSize.x + "/" + texSize.y);
var pingpong = [
makeTexture(gl, texSize.x, texSize.y, gl.FLOAT),
makeTexture(gl, texSize.x, texSize.y, gl.FLOAT)
];
var framebuffer = gl.createFramebuffer();
var fps = [];
var last = 0;
var which;
function draw (time) {
if (time - last < 1000 / 120)
return requestAnimationFrame(draw);
fps.unshift(1000 / (time - last));
if (fps.length > 10) fps.length = 10;
last = time;
//console.log(fps.reduce(function (a, b) { return a + b; }, 0) / fps.length);
// for (var i = 0; i < 2; i++) {
which = !which - 0;
gl.useProgram(shaders.buf.pgm);
gl.bindBuffer(gl.ARRAY_BUFFER, quad);
gl.enableVertexAttribArray(shaders.buf.coord);
gl.vertexAttribPointer(shaders.buf.coord, 2, gl.FLOAT, gl.FALSE, 8, 0);
gl.bindTexture(gl.TEXTURE_2D, pingpong[!which - 0]);
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(
gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D, pingpong[which], 0
);
gl.viewport(0, 0, texSize.x, texSize.y);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
// }
gl.useProgram(shaders.scr.pgm);
gl.bindBuffer(gl.ARRAY_BUFFER, quad);
gl.enableVertexAttribArray(shaders.scr.coord);
gl.vertexAttribPointer(shaders.scr.coord, 2, gl.FLOAT, gl.FALSE, 8, 0);
gl.bindTexture(gl.TEXTURE_2D, pingpong[which]);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, (innerHeight - innerWidth) / 2, innerWidth, innerWidth);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(draw);
}
draw();
}, false);
</script>
</head>
<body>
<canvas></canvas>
</body>
</html>