WebGPU - More Than Just Graphics
This new API unlocks many new possibilities for web developers, which were only possible outside the browser before.
As someone who enjoys working with graphics, I was surprised by the capabilities of the WebGPU API.
Without a doubt, this new API could change how people do GPU computing. Instead of downloading and running pre-trained AI models, this would now work directly from the browser - no setup required.
In this blog post, I want to give you an overview of the API, its origins, and its capabilities.
What is WebGPU?
In essence, it’s a modern browser API for interacting with your computers graphics processing unit (GPU). This allows for high-performance rendering and computations.
But don’t confuse it with WebGL! WebGPU is a modernized, up-to-date and more powerful version of its predecessor WebGL.
WebGL - just graphics
WebGL was originally based on OpenGL, a native API standard for GPUs. But since then GPUs evolved and so did their APIs.
We now have modern APIs like Vulkan, DirectX and Metal. OpenGL on the other side has no new updates planned.
But there’s a bigger problem: WebGL is based upon the idea of drawing graphics. Nowadays GPUs are much more versatile - they’re being used for machine learning, cryptography, simulations or data analysis
So what we need is a browser API based on modern GPU APIs which can also be used versatile. This is where WebGPU comes in.
WebGPU - more than just graphics
Of course WebGPU still offers a huge variety of rendering capabilities and lots of features, which are too broad to explain in a single blogpost.
Instead I want to show you how you can make use of your graphics card - right from your browser - to run heavy computations.
This of course comes with an additional overhead. One really needs to evaluate if this solution is worth the effort. As a starting point it might be helpful to see why you would want to make use of the GPU.
Why GPU?
One large problem with javascript is that it runs on a single thread. Therefore running heavy computations client-side could potentially block your UI from being responsive.
Where your GPU really shines is when you have a task which is parallelizable. For example if you have a large dataset which you want to do computations on. If the data rows do not depend on each other, all calculations can be done in parallel.
Or as another example, if you have data which needs to be transformed lots of times before the result is shown to the user. Like for example a force-directed graph, where you first need to simulate the motion of edges or run a minimization algorithm to get a nice looking graph.
But be aware! For simple problems using a Web Worker might be the better solution.
Deep dive into compute pipelines
I want to give a (very) rough example how a computation on the GPU might look like using the WebGPU API. For a general introduction please refer to the MDN docs.
We do this in the following steps:
- Define the data we are working with
- Write the code that interacts with the data
- Configure how the GPU should execute the code
- Copy the initial data to the GPU
- Run the computation
- Read out the result
You start off by requesting a gpu adapter and device:
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
To do any computation you first tell your GPU a bit about the data you want to allocate and its usage.
const stagingBuffer = device.createBuffer({
size: BUFFER_SIZE, // Buffer size, in bytes
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
});
const outputBuffer = device.createBuffer({ ... });
Then you define what needs to be done with the data with a Rust-like language named WebGPU Shading Language (WGSL). It compiles to native instructions on the GPU.
const shaderModule = device.createShaderModule({ code: [`... WGSL code ...`] });
In the end a pipeline is needed to connect data and code together.
const pipeline = device.createComputePipeline({ ... });
Then its just a matter of copying the initial data to the GPU and running the pipeline (which I don’t want to do in detail) the results then can be read back into javascript:
commandEncoder.copyBufferToBuffer(outputBuffer, 0, stagingBuffer, 0, BUFFER_SIZE);
device.queue.submit([commandEncoder.finish()])
await stagingBuffer.mapAsync(GPUMapMode.READ, 0, BUFFER_SIZE);
const copyArrayBuffer = stagingBuffer.getMappedRange(0, BUFFER_SIZE);
const data = copyArrayBuffer.slice();
stagingBuffer.unmap();
console.log(new Float32Array(data));
Why this is such a big step for the web
Machine learning is currently one of the fastest growing areas. So far most machine learning interfaces either work with an additional backend or need you to download and run code locally. With WebGPU, both development and execution can be shifted to the browser. Many ML-frameworks like TensorFlow.js are already using it.
Additionally, as we have access to all these new GPU APIs now, games and game engines can run efficiently in your browser without performance differences compared to native execution. 3D rendering libraries like Three.js already support WebGPU as a backend.
Although all development environments have their up- and downsides, web-development is the one with the biggest ecosystem, it’s rapidly growing every year and the most accessible for everyone. As the browsers are adding more and more native features, the gap between web and native closes. If you’re interested in more new native features, check out the blog post about PWA.
Conclusion
In the next few years, WebGPU will become more and more important for the ecosystem.
Although I left out a lot of code in my example, the overhead is still very large. It requires some low-level knowledge and has a steep learning curve at first. But of course there are many resources and libraries which can help you in the process.
As its a rather new API, the browser support is also still somewhat restricted.
Even if you are not working with ML, I still think it’s worth to keep it in mind as a possible solution. Sometimes javascript - although being very well optimised - can be pushed to its limits, where only a native solution can help you out. And besides WebAssembly this is probably the best way to optimize heavy calculations for performance.