Created
May 13, 2012 19:08
-
-
Save jussi-kalliokoski/2689799 to your computer and use it in GitHub Desktop.
Parallel fragment shaders in JavaScript using Workers. Not very optimized, but works. Hard to find the perfect SHADER_COUNT for a certain dimension. Usually it's less the shaders the better.
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
/*jshint asi:true, esnext:true */ | |
void function () { | |
const SHADER_COUNT = 8 | |
const WIDTH = 800 | |
const HEIGHT = 800 | |
var rAF = | |
window.requestAnimationFrame || | |
window.webkitRequestAnimationFrame || | |
window.mozRequestAnimationFrame || | |
window.oRequestAnimationFrame || | |
window.msRequestAnimationFrame || | |
function (cb) { | |
return window.setTimeout(cb, 1000 / 60) | |
} | |
function onMessage (e) { | |
var id = this._state.id | |
readyStates[id] = true | |
shaderData[id] = new Uint8Array(e.data) | |
if (readyStates.filter(function (s) { | |
return !!s | |
}).length === readyStates.length) { | |
rAF(draw) | |
} | |
} | |
var c = document.getElementById('c') | |
var w = c.width = WIDTH | |
var h = c.height = HEIGHT | |
var ctx = c.getContext('2d') | |
var iD = ctx.getImageData(0, 0, w, h) | |
var shaders = [] | |
var readyStates = [] | |
var shaderStates = [] | |
var shaderData = [] | |
void function initShaders () { | |
var l, i | |
l = ~~(iD.data.length / SHADER_COUNT / 4) * 4 | |
for (var i=0, s=0; i < SHADER_COUNT-1; i++, s+=l) { | |
shaderStates[i] = { | |
id: i, | |
w: w, | |
h: h, | |
s: s, | |
f: 0 | |
} | |
shaderData[i] = new Uint8Array(l) | |
} | |
shaderStates[i] = { | |
id: i, | |
w: w, | |
h: h, | |
s: s, | |
f: 0 | |
} | |
shaderData[i] = new Uint8Array(iD.data.length - s) | |
for (i=0; i<SHADER_COUNT; i++) { | |
shaders[i] = new Worker('shader.js') | |
shaders[i].onmessage = onMessage | |
shaders[i]._state = shaderStates[i] | |
readyStates[i] = false | |
} | |
}() | |
var rT | |
function draw () { | |
var i, n, m | |
var d = iD.data | |
for (i=0, m=0; i<SHADER_COUNT; m+=shaderData[i].length, i++) { | |
try { | |
d.subarray(m, m + shaderData[i].length).set( | |
shaderData[i]) | |
} catch (e) { throw m + ' ' + shaderData[i].length + ' ' + i + ' ' + d.length } | |
} | |
ctx.putImageData(iD, 0, 0) | |
parallelize() | |
} | |
function parallelize () { | |
for (var i=0; i<SHADER_COUNT; i++) { | |
readyStates[i] = false | |
shaders[i].postMessage(shaderStates[i]) | |
shaders[i].postMessage(shaderData[i].buffer, | |
[shaderData[i].buffer]) | |
shaderStates[i].f += 1 | |
} | |
rT = +new Date() | |
} | |
/* start the weird async loop */ | |
parallelize() | |
}() |
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
/*jshint asi:true, esnext:true */ | |
var state | |
this.onmessage = function (e) { | |
if (String(e.data) === '[object Object]') { | |
state = e.data | |
return | |
} | |
var i, n, x, y, a | |
var img = new Uint8Array(e.data) | |
var l = img.length | |
var f = state.f | |
var xf = 2 / state.w | |
var yf = 2 / state.w / state.h | |
for (i=0, n=state.s/4; i<l; i+=4, n++) { | |
x = (n % state.w) * xf - 1.0 | |
y = n * yf - 1.0 | |
a = Math.abs(Math.sin(Math.atan2(x, y) + f * 0.02)) | |
img[i+0] = 255 * Math.abs(x) * a | |
img[i+1] = 255 * Math.abs(y) * (1.0 - a) | |
img[i+2] = 255 * Math.sqrt(Math.abs(x * y)) | |
img[i+3] = 255 | |
} | |
this.postMessage(img.buffer, [img.buffer]) | |
} |
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 lang="en"> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | |
</head> | |
<body> | |
<canvas id="c"></canvas> | |
<script src="shade.js"></script> | |
</body> | |
</html> |
So really what I should be doing is running all my per-pixel calculations in one chunk, saving into a Uint8Array, and then assigning en-masse into the imagedata using the typed array methods?
It's worth the shot! Seems that assigning to the Uint8ClampedArray is sloooow, so maybe that will help.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Explanation of the typed array optimization: