Last active
November 8, 2024 02:24
-
-
Save adrianseeley/9fd4e0a28e8f559646c4 to your computer and use it in GitHub Desktop.
QP GPU (queue-pee gee-pee-you, or just Q-P for short) Quantum Particles via Graphics Processing Unit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
window.onerror = function (msg, url, lineno) { | |
alert(url + '(' + lineno + '): ' + msg); | |
} | |
function createShader (str, type) { | |
var shader = gl.createShader(type); | |
gl.shaderSource(shader, str); | |
gl.compileShader(shader); | |
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) | |
throw gl.getShaderInfoLog(shader); | |
return shader; | |
} | |
function createProgram (vstr, fstr) { | |
var OES_texture_float = gl.getExtension('OES_texture_float'); | |
if (!OES_texture_float) | |
throw new Error("No support for OES_texture_float"); | |
var program = gl.createProgram(); | |
var vshader = createShader(vstr, gl.VERTEX_SHADER); | |
var fshader = createShader(fstr, gl.FRAGMENT_SHADER); | |
gl.attachShader(program, vshader); | |
gl.attachShader(program, fshader); | |
gl.linkProgram(program); | |
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) | |
throw gl.getProgramInfoLog(program); | |
return program; | |
} | |
function screenQuad () { | |
var vertexPosBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer); | |
var vertices = [-1, -1, 1, -1, -1, 1, 1, 1]; | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); | |
vertexPosBuffer.itemSize = 2; | |
vertexPosBuffer.numItems = 4; | |
/* | |
2___3 | |
|\ | | |
| \ | | |
|__\| | |
0 1 | |
*/ | |
return vertexPosBuffer; | |
} | |
function linkProgram (program) { | |
var vshader = createShader(program.vshaderSource, gl.VERTEX_SHADER); | |
var fshader = createShader(program.fshaderSource, gl.FRAGMENT_SHADER); | |
gl.attachShader(program, vshader); | |
gl.attachShader(program, fshader); | |
gl.linkProgram(program); | |
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { | |
throw gl.getProgramInfoLog(program); | |
} | |
} | |
function loadFile (file, callback, noCache, isJson) { | |
var request = new XMLHttpRequest(); | |
request.onreadystatechange = function() { | |
if (request.readyState == 1) { | |
if (isJson) | |
request.overrideMimeType('application/json'); | |
request.send(); | |
} else if (request.readyState == 4) { | |
if (request.status == 200) | |
callback(request.responseText); | |
else if (request.status == 404) | |
throw 'File "' + file + '" does not exist.'; | |
else | |
throw 'XHR error ' + request.status + '.'; | |
} | |
}; | |
var url = file; | |
if (noCache) | |
url += '?' + (new Date()).getTime(); | |
request.open('GET', url, true); | |
} | |
function loadProgram (vs, fs, callback) { | |
var program = gl.createProgram(); | |
function vshaderLoaded(str) { | |
program.vshaderSource = str; | |
if (program.fshaderSource) { | |
linkProgram(program); | |
callback(program); | |
} | |
} | |
function fshaderLoaded (str) { | |
program.fshaderSource = str; | |
if (program.vshaderSource) { | |
linkProgram(program); | |
callback(program); | |
} | |
} | |
loadFile(vs, vshaderLoaded, true); | |
loadFile(fs, fshaderLoaded, true); | |
return program; | |
} | |
(function() { | |
var lastTime = 0; | |
var vendors = ['ms', 'moz', 'webkit', 'o']; | |
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { | |
window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; | |
window.cancelRequestAnimationFrame = window[vendors[x] + 'CancelRequestAnimationFrame']; | |
} | |
if (!window.requestAnimationFrame) | |
window.requestAnimationFrame = function (callback, element) { | |
var currTime = new Date().getTime(); | |
var timeToCall = Math.max(0, 16 - (currTime - lastTime)); | |
var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); | |
lastTime = currTime + timeToCall; | |
return id; | |
}; | |
if (!window.cancelAnimationFrame) | |
window.cancelAnimationFrame = function (id) { | |
clearTimeout(id); | |
}; | |
}()) | |
function prepScreenQuad () { | |
var vertexPosBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer); | |
var vertices = [-1, -1, 1, -1, -1, 1, 1, 1]; | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); | |
vertexPosBuffer.itemSize = 2; | |
vertexPosBuffer.numItems = 4; | |
/* | |
2 ___ 3 | |
|\ | | |
| \ | | |
0|__\|1 | |
*/ | |
return vertexPosBuffer; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html style="text-align: center;"> | |
<canvas id="c" width="128" height="128" style="display: none;"></canvas> | |
<canvas id="d" width="512" height="512"></canvas> | |
<script src="glutil.js"></script> | |
<script id="vshader" type="text/plain"> | |
attribute vec2 vtxpos; | |
varying vec2 texpos; | |
void main() { | |
texpos = (vtxpos / 2.) + vec2(0.5, 0.5); | |
gl_Position = vec4(vtxpos, 0, 1); | |
} | |
</script> | |
<script id="fshader" type="text/plain"> | |
precision mediump float; | |
varying vec2 texpos; | |
uniform sampler2D PARTICLETEX; | |
uniform sampler2D ATTRACTIONTEX; | |
uniform int PASS; | |
uniform float COEFDRAG; | |
uniform float COEFACELSCALE; | |
float shift_right (float v, float amt) { | |
v = floor(v) + 0.5; | |
return floor(v / exp2(amt)); | |
} | |
float shift_left (float v, float amt) { | |
return floor(v * exp2(amt) + 0.5); | |
} | |
float mask_last (float v, float bits) { | |
return mod(v, shift_left(1.0, bits)); | |
} | |
float extract_bits (float num, float from, float to) { | |
from = floor(from + 0.5); to = floor(to + 0.5); | |
return mask_last(shift_right(num, from), to - from); | |
} | |
vec4 encode_float (float val) { | |
if (val == 0.0) return vec4(0, 0, 0, 0); | |
float sign = val > 0.0 ? 0.0 : 1.0; | |
val = abs(val); | |
float exponent = floor(log2(val)); | |
float biased_exponent = exponent + 127.0; | |
float fraction = ((val / exp2(exponent)) - 1.0) * 8388608.0; | |
float t = biased_exponent / 2.0; | |
float last_bit_of_biased_exponent = fract(t) * 2.0; | |
float remaining_bits_of_biased_exponent = floor(t); | |
float byte4 = extract_bits(fraction, 0.0, 8.0) / 255.0; | |
float byte3 = extract_bits(fraction, 8.0, 16.0) / 255.0; | |
float byte2 = (last_bit_of_biased_exponent * 128.0 + extract_bits(fraction, 16.0, 23.0)) / 255.0; | |
float byte1 = (sign * 128.0 + remaining_bits_of_biased_exponent) / 255.0; | |
return vec4(byte4, byte3, byte2, byte1); | |
} | |
float vupdate (float vn) { | |
// calculate this particles number | |
float this_particle_number = ((texpos.y * PARTICLES_TALL) * PARTICLES_TALL) + (texpos.x * PARTICLES_WIDE); | |
// iterate through all particles | |
for (float x = 0.0; x < PARTICLES_WIDE; x++) { | |
for (float y = 0.0; y < PARTICLES_TALL; y++) { | |
// create texpos for other particle | |
vec2 that_texpos = vec2(x / PARTICLES_WIDE, y / PARTICLES_TALL); | |
// calculate the vector to the other particle (that - this) | |
vec2 vector_to_particle = vec2(texture2D(PARTICLETEX, that_texpos).b, texture2D(PARTICLETEX, that_texpos).a) - vec2(texture2D(PARTICLETEX, texpos).b, texture2D(PARTICLETEX, texpos).a); | |
// calculate that particle number | |
float that_particle_number = (y * PARTICLES_TALL) + x; | |
// lookup attraction pixel | |
vec4 attraction_pixel = texture2D(ATTRACTIONTEX, vec2(floor(this_particle_number / 4.0) / (PARTICLES_TOTAL / 4.0), that_particle_number / PARTICLES_TOTAL)); | |
// calculate which component of attraction pixel | |
float attraction_color = floor(mod(this_particle_number, 4.0)); | |
// determine attraction value (this is a switch case for rgb or a) | |
float attraction_value; | |
if (attraction_color == 0.0) attraction_value = attraction_pixel[0]; | |
else if (attraction_color == 1.0) attraction_value = attraction_pixel[1]; | |
else if (attraction_color == 2.0) attraction_value = attraction_pixel[2]; | |
else if (attraction_color == 3.0) attraction_value = attraction_pixel[3]; | |
// determine gravity value | |
float gravity_value = 1.;//length(vector_to_particle); | |
// grab the right component and update vn | |
if (PASS == 0) { // vx | |
vn += normalize(vector_to_particle).x * attraction_value * gravity_value * COEFACELSCALE; | |
} else if (PASS == 1) { // pass == 1, vy | |
vn += normalize(vector_to_particle).y * attraction_value * gravity_value * COEFACELSCALE; | |
} | |
} | |
} | |
// drag | |
vn *= COEFDRAG; | |
// return updated vn | |
return vn; | |
} | |
void main() { | |
if (PASS == 0) { // vx | |
// update vx | |
gl_FragColor = encode_float(vupdate(texture2D(PARTICLETEX, texpos).r)); | |
} else if (PASS == 1) { // vy | |
// update vx | |
gl_FragColor = encode_float(vupdate(texture2D(PARTICLETEX, texpos).g)); | |
} else if (PASS == 2) { // px | |
// px += vx; | |
gl_FragColor = encode_float(clamp(texture2D(PARTICLETEX, texpos).b + texture2D(PARTICLETEX, texpos).r, -1000., 1000.)); | |
} else if (PASS == 3) { // py | |
// py += vy; | |
gl_FragColor = encode_float(clamp(texture2D(PARTICLETEX, texpos).a + texture2D(PARTICLETEX, texpos).g, -1000., 1000.)); | |
} | |
} | |
</script> | |
<script> | |
/* | |
Textures are power of two, you want to be as close | |
as possible without going over to avoid waste. | |
1 x 1 = 1 | |
2 x 2 = 4 | |
4 x 4 = 16 | |
8 x 8 = 64 | |
16 x 16 = 256 | |
32 x 32 = 1024 | |
64 x 64 = 4096 | |
128 x 128 = 16384 | |
256 x 256 = 65536 | |
512 x 512 = 262144 | |
1024 x 1024 = 1048576 | |
2056 x 2056 = 4227136 | |
*/ | |
var CONFIG_INITIALIZATION_RADIUS = 0.01; | |
var CONFIG_VISUAL_DRAW_RADIUS_PX = 3; | |
var CONFIG_VISUAL_DRAW_BORDER_PX = 20; | |
var CONFIG_VISUAL_CLEAR_COLOR = 'rgba(255, 255, 255, 0.1)'; | |
var CONFIG_NUMBER_OF_PARTICLES = 4096; | |
var CONFIG_PARITCLE_TEXTURE_SIZE = 64; | |
var CONFIG_ATTRACTOR_LAMBDA = function (a, b) { | |
if (a == null || b == null) return 0; | |
return b[0] - a[0]; | |
}; | |
var CONFIG_SET_UNIFORMS_LAMBDA = function () { | |
// set drag | |
gl.uniform1f(program.coefdrag, 1); | |
// set acel scale | |
gl.uniform1f(program.coefacelscale, 1); | |
}; | |
var CONFIG_VECTOR_CLASSES = []; | |
for (var test = 0; test < CONFIG_NUMBER_OF_PARTICLES; test++) | |
CONFIG_VECTOR_CLASSES.push([[test], [test], 'rgb(' + Math.floor((test / CONFIG_NUMBER_OF_PARTICLES) * 255) + ', 0, 0)']); | |
for (var shuf = 0; shuf < 10000; shuf++) { | |
var a = Math.floor(Math.random() * CONFIG_VECTOR_CLASSES.length); | |
var b = Math.floor(Math.random() * CONFIG_VECTOR_CLASSES.length); | |
var hold = CONFIG_VECTOR_CLASSES[a]; | |
CONFIG_VECTOR_CLASSES[a] = CONFIG_VECTOR_CLASSES[b]; | |
CONFIG_VECTOR_CLASSES[b] = hold; | |
} | |
var c = document.getElementById('c'); | |
c.width = CONFIG_PARITCLE_TEXTURE_SIZE; | |
c.height = CONFIG_PARITCLE_TEXTURE_SIZE; | |
var d = document.getElementById('d'); | |
var ctx = d.getContext('2d'); | |
var vs = document.getElementById('vshader').textContent; | |
var fs = document.getElementById('fshader').textContent | |
.split('PARTICLES_WIDE').join(c.width + '.') | |
.split('PARTICLES_TALL').join(c.height + '.') | |
.split('PARTICLES_TOTAL').join(CONFIG_NUMBER_OF_PARTICLES + '.'); | |
var gl = c.getContext('experimental-webgl'); | |
var program = createProgram(vs, fs); | |
prepScreenQuad(); | |
gl.useProgram(program); | |
gl.enableVertexAttribArray(program.vertexPosArray); | |
gl.vertexAttribPointer(program.vtxpos, 2, gl.FLOAT, false, 0, 0); | |
program.vtxpos = gl.getAttribLocation(program, 'vtxpos'); | |
program.sampler = [gl.getUniformLocation(program, 'PARTICLETEX'), gl.getUniformLocation(program, 'ATTRACTIONTEX')]; | |
program.pass = gl.getUniformLocation(program, 'PASS'); | |
program.coefdrag = gl.getUniformLocation(program, 'COEFDRAG'); | |
program.coefacelscale = gl.getUniformLocation(program, 'COEFACELSCALE'); | |
program.tex = [gl.createTexture(), gl.createTexture()]; | |
program.texsize = [[CONFIG_PARITCLE_TEXTURE_SIZE, CONFIG_PARITCLE_TEXTURE_SIZE], [CONFIG_NUMBER_OF_PARTICLES / 4, CONFIG_NUMBER_OF_PARTICLES]]; | |
program.texbuff = [new Float32Array(4 * program.texsize[0][0] * program.texsize[0][1]), new Float32Array(4 * program.texsize[1][0] * program.texsize[1][1])]; | |
program.RDBUFF = new Uint8Array(4 * c.width * c.height); | |
program.VXBUFF = new Float32Array(program.texsize[0][0] * program.texsize[0][1]); | |
program.VYBUFF = new Float32Array(program.texsize[0][0] * program.texsize[0][1]); | |
program.PXBUFF = new Float32Array(program.texsize[0][0] * program.texsize[0][1]); | |
program.PYBUFF = new Float32Array(program.texsize[0][0] * program.texsize[0][1]); | |
program.BUFFS = [program.VXBUFF, program.VYBUFF, program.PXBUFF, program.PYBUFF]; | |
// establish PARTICLETEX | |
for (var i = 0; i < program.texbuff[0].length; i += 4) { | |
program.texbuff[0][i + 0] = CONFIG_INITIALIZATION_RADIUS * Math.cos((i / 4) / CONFIG_NUMBER_OF_PARTICLES * Math.PI * 2); // px | |
program.texbuff[0][i + 1] = CONFIG_INITIALIZATION_RADIUS * Math.sin((i / 4) / CONFIG_NUMBER_OF_PARTICLES * Math.PI * 2); // py | |
program.texbuff[0][i + 2] = 0; // vx | |
program.texbuff[0][i + 3] = 0; // vy | |
} | |
// establish ATTRACTIONTEX | |
var i3 = 0; | |
for (var i = 0; i < CONFIG_PARITCLE_TEXTURE_SIZE * CONFIG_PARITCLE_TEXTURE_SIZE; i++) | |
for (var i2 = 0; i2 < CONFIG_PARITCLE_TEXTURE_SIZE * CONFIG_PARITCLE_TEXTURE_SIZE; i2++) | |
// calculate attraction between particles vector classes (or if we have empty particle slots, pass null) | |
program.texbuff[1][i3++] = CONFIG_ATTRACTOR_LAMBDA(CONFIG_VECTOR_CLASSES[i] || null, CONFIG_VECTOR_CLASSES[i2] || null); | |
function draw () { | |
// find caps | |
var xmax = program.texbuff[0][0]; | |
var xmin = program.texbuff[0][0]; | |
var ymax = program.texbuff[0][1]; | |
var ymin = program.texbuff[0][1]; | |
// skip the first as it is our starting point, iterate by pixel (particle) | |
for (var i = 4; i < program.texbuff[0].length && i / 4 < CONFIG_NUMBER_OF_PARTICLES; i += 4) { | |
if (program.texbuff[0][i + 0] > xmax) xmax = program.texbuff[0][i + 0]; // px > xmax ? xmax = px | |
if (program.texbuff[0][i + 0] < xmin) xmin = program.texbuff[0][i + 0]; // px < xmin ? xmin = px | |
if (program.texbuff[0][i + 1] > ymax) ymax = program.texbuff[0][i + 1]; // py > ymax ? ymax = py | |
if (program.texbuff[0][i + 1] < ymin) ymin = program.texbuff[0][i + 1]; // py < ymin ? ymin = py | |
} | |
// define ranges | |
var xrng = xmax - xmin; | |
var yrng = ymax - ymin; | |
// clear | |
ctx.fillStyle = CONFIG_VISUAL_CLEAR_COLOR; | |
ctx.fillRect(0, 0, d.width, d.height); | |
// draw particles | |
for (var i = 0; i < program.texbuff[0].length && i / 4 < CONFIG_NUMBER_OF_PARTICLES; i += 4) { | |
ctx.beginPath(); | |
ctx.fillStyle = CONFIG_VECTOR_CLASSES[i / 4][2]; | |
ctx.arc((((program.texbuff[0][i + 0] - xmin) / xrng) * (d.width - (CONFIG_VISUAL_DRAW_BORDER_PX * 2))) + CONFIG_VISUAL_DRAW_BORDER_PX, (((program.texbuff[0][i + 1] - ymin) / yrng) * (d.height - (CONFIG_VISUAL_DRAW_BORDER_PX * 2))) + CONFIG_VISUAL_DRAW_BORDER_PX, CONFIG_VISUAL_DRAW_RADIUS_PX, 0, 2 * Math.PI, false); | |
ctx.fill(); | |
} | |
}; | |
function blit () { | |
// set uniforms | |
CONFIG_SET_UNIFORMS_LAMBDA(); | |
// load textures | |
for (var t = 0; t < program.tex.length; t++) { | |
// set the active texture to the appropriate texture reference | |
gl.activeTexture(gl.TEXTURE0 + t); | |
// bind the appropriate texture | |
gl.bindTexture(gl.TEXTURE_2D, program.tex[t]); | |
// copy actual texture data to GPU | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, program.texsize[t][0], program.texsize[t][1], 0, gl.RGBA, gl.FLOAT, program.texbuff[t]); | |
// set flags to render pixel perfect | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); | |
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); | |
// tell sampler to use appropriate texture unit | |
gl.uniform1i(program.sampler[t], t); | |
}; | |
// make draw passes | |
for (var pass = 0; pass < 4; pass++) { | |
// set pass | |
gl.uniform1i(program.pass, pass); | |
// make draw call | |
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); | |
// read output to ubyte buffer | |
gl.readPixels(0, 0, c.width, c.height, gl.RGBA, gl.UNSIGNED_BYTE, program.RDBUFF); | |
// copy to appropriate pass buffer | |
program.BUFFS[pass].set(new Float32Array(program.RDBUFF.buffer)); | |
} | |
// transpose new values onto state texture | |
for (var i = 0; i < program.texbuff[0].length; i += 4) { | |
program.texbuff[0][i + 0] = program.PXBUFF[i / 4]; // px | |
program.texbuff[0][i + 1] = program.PYBUFF[i / 4]; // py | |
program.texbuff[0][i + 2] = program.VXBUFF[i / 4]; // vx | |
program.texbuff[0][i + 3] = program.VYBUFF[i / 4]; // vy | |
} | |
}; | |
function iter () { | |
requestAnimationFrame(iter); | |
var t = new Date().getTime(); | |
blit(); | |
draw(); | |
console.log(new Date().getTime() - t); | |
//setTimeout(iter, 1000); | |
}; | |
draw(); | |
iter(); | |
</script> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment