Skip to content

Instantly share code, notes, and snippets.

@jussi-kalliokoski
Created May 13, 2012 19:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jussi-kalliokoski/2689799 to your computer and use it in GitHub Desktop.
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.
/*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()
}()
/*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])
}
<!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>
@jussi-kalliokoski
Copy link
Author

Explanation of the typed array optimization:

/* chech if imagedata.data is a Uint8ClampedArray */
if (d.set && d.subarray) {
    /* loop through the Uint8Arrays.
     * m is the cursor, so we append the length of the current array
     * to the cursor after each iteration */
    for (i=0, m=0; i<readyStates.length; m+=readyStates[i].length, i++) {
        /* get the part of the imagedata we are changing and set
        * its values to those in the Uint8Array */
        d.subarray(m, m + readyStates[i].length).set(
            readyStates[i])
    }
/* fall back to manual copying of the values */
} else { ...

@cgiffard
Copy link

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?

@jussi-kalliokoski
Copy link
Author

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