Alex Crichton 25b26f41e7 Implement support for WebAssembly threads
... and add a parallel raytracing demo!

This commit adds enough support to `wasm-bindgen` to produce a workable
wasm binary *today* with the experimental WebAssembly threads support
implemented in Firefox Nightly. I've tried to comment what's going on in
the commits and such, but at a high level the changes made here are:

* A new transformation, living in a new `wasm-bindgen-threads-xform`
  crate, prepares a wasm module for parallel execution. This performs a
  number of mundane tasks which I hope to detail in a blog post later on.
* The `--no-modules` output is enhanced with more support for when
  shared memory is enabled, allowing passing in the module/memory to
  initialize the wasm instance on multiple threads (sharing both module
  and memory).
* The `wasm-bindgen` crate now offers the ability, in `--no-modules`
  mode, to get a handle on the `WebAssembly.Module` instance.
* The example itself requires Xargo to recompile the standard library
  with atomics and an experimental feature enabled. Afterwards it
  experimentally also enables threading support in wasm-bindgen.

I've also added hopefully enough CI support to compile this example in a
builder so we can upload it and poke around live online. I hope to
detail more about the technical details here in a blog post soon as
well!
2018-10-23 01:20:18 -07:00

120 lines
3.2 KiB
JavaScript

const button = document.getElementById('render');
const canvas = document.getElementById('canvas');
const scene = document.getElementById('scene');
const concurrency = document.getElementById('concurrency');
const concurrencyAmt = document.getElementById('concurrency-amt');
const timing = document.getElementById('timing');
const timingVal = document.getElementById('timing-val');
const ctx = canvas.getContext('2d');
button.disabled = true;
concurrency.disabled = true;
// First up, but try to do feature detection to provide better error messages
function loadWasm() {
if (typeof SharedArrayBuffer !== 'function') {
alert('this browser does not have SharedArrayBuffer support enabled');
return
}
// Test for bulk memory operations with passive data segments
// (module (memory 1) (data passive ""))
const buf = new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x05, 0x03, 0x01, 0x00, 0x01, 0x0b, 0x03, 0x01, 0x01, 0x00]);
if (!WebAssembly.validate(buf)) {
alert('this browser does not support passive wasm memory, demo does not work');
return
}
wasm_bindgen('./raytrace_parallel_bg.wasm')
.then(run)
.catch(console.error);
}
loadWasm();
const { Scene, WorkerPool } = wasm_bindgen;
function run() {
// The maximal concurrency of our web worker pool is `hardwareConcurrency`,
// so set that up here and this ideally is the only location we create web
// workers.
pool = new WorkerPool(navigator.hardwareConcurrency);
// Configure various buttons and such.
button.onclick = function() {
console.time('render');
let json;
try {
json = JSON.parse(scene.value);
} catch(e) {
alert(`invalid json: ${e}`);
return
}
canvas.width = json.width;
canvas.height = json.height;
render(new Scene(json));
};
button.innerText = 'Render!';
button.disabled = false;
concurrency.oninput = function() {
concurrencyAmt.innerText = 'Concurrency: ' + concurrency.value;
};
concurrency.min = 1;
concurrency.step = 1;
concurrency.max = navigator.hardwareConcurrency;
concurrency.value = concurrency.max;
concurrency.oninput();
concurrency.disabled = false;
}
let rendering = null;
let start = null;
let interval = null;
let pool = null;
class State {
constructor(wasm) {
this.start = performance.now();
this.wasm = wasm;
this.running = true;
this.counter = 1;
this.interval = setInterval(() => this.updateTimer(), 100);
wasm.promise()
.then(() => {
this.updateTimer();
this.stop();
})
.catch(console.error);
}
updateTimer() {
const dur = performance.now() - this.start;
timingVal.innerText = `${dur}ms`;
this.counter += 1;
if (this.wasm && this.counter % 3 == 0)
this.wasm.requestUpdate();
}
stop() {
if (!this.running)
return;
console.timeEnd('render');
this.running = false;
pool = this.wasm.cancel(); // this frees `wasm`, returning the worker pool
this.wasm = null;
clearInterval(this.interval);
}
}
function render(scene) {
if (rendering) {
rendering.stop();
rendering = null;
}
rendering = new State(scene.render(concurrency.value, pool, ctx));
pool = null; // previous call took ownership of `pool`, zero it out here too
}