Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

This comment has been minimized.

Copy link
Owner Author

@jussi-kalliokoski jussi-kalliokoski commented May 14, 2012

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

This comment has been minimized.

Copy link

@cgiffard cgiffard commented May 14, 2012

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

This comment has been minimized.

Copy link
Owner Author

@jussi-kalliokoski jussi-kalliokoski commented May 14, 2012

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