3.4 Using Web Workers
To enhance performance with WebGPU, you can execute it in a Web Worker. This approach allows the rendering workload to be offloaded to a worker, freeing up the main thread for other tasks. Here’s a step-by-step guide on how to achieve this.
Launch Playground - 3_04_workerThe worker script handles the rendering process. Below is a general structure of the worker script:
self.addEventListener('message', (ev) => {
switch (ev.data.type) {
case 'run': {
try {
run(ev.data.offscreenCanvas, ev.data.code);
} catch (err) {
self.postMessage({
type: 'log',
message: `Error while initializing WebGPU in worker process: ${err.message}`,
});
}
break;
}
}
});
async function run(canvas, code) {
let angle = 0.0;
const adapter = await navigator.gpu.requestAdapter();
let device = await adapter.requestDevice();
let context = configContext(device, canvas);
async function render() {
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
In this script, we add an event listener to handle messages from the main thread. This listener acts as the entry point for the rendering process. When a message is received, it includes three pieces of data: the message type, shader code, and the offscreen canvas—all part of the message payload.
Upon receiving the message, we check its type and pass both the offscreen canvas and shader code to the run function. This function performs rendering similarly to how it did before, but the specifics are not detailed here.
Next, let’s see how to configure the main thread to communicate with the worker and start the rendering process:
const worker = new Worker('./worker.js');
try {
const offscreenCanvas = canvas.transferControlToOffscreen();
const devicePixelRatio = window.devicePixelRatio || 1;
offscreenCanvas.width = canvas.clientWidth * devicePixelRatio;
offscreenCanvas.height = canvas.clientHeight * devicePixelRatio;
let code = document.getElementById('teapot_shader').innerText;
worker.postMessage({ type: 'run', offscreenCanvas, code }, [offscreenCanvas]);
} catch (err) {
console.warn(err.message);
worker.terminate();
}
Here’s what’s happening in this code:
We first load the worker script.
We then convert the canvas to an offscreen canvas using
transferControlToOffscreen
and adjust its size based on devicePixelRatio.We retrieve the shader code as a string.
Finally, we send a run message to the worker with the offscreen canvas and shader code. Note that the offscreen canvas must be included both in the message payload and in the transferable objects array (the second parameter of
postMessage
).
In JavaScript, a transferable object has underlying resources unsafe for simultaneous access by multiple threads. Transferring the object's ownership to the worker thread is a safe way to handle this. You request this transfer by including the object in the transferable objects array (the second parameter of postMessage
).
With these steps, you can effectively utilize workers for rendering tasks.