WebGPU Support
I'm creating this issue as a status tracker for an implementation that we actually want to upstream. There have been 2 proof of concept approaches attempted recently, and here are some notes on these initial approaches along with some Pros/Cons of each to help aid any future direction:
Approach 1: DIY
Initially I went the route of starting our own WGSL compiler and native GPU API.
NOTE: The PR and branches listed here were all very rough and incomplete, with many FIXMEs that would've needed to be addressed before anything was upstreamable
- Draft PR/branch for initial WGSL Compiler and WebGPU Vulkan backend: https://github.com/LadybirdBrowser/ladybird/pull/4958
- Draft branch for initial WebGPU Metal backend: https://github.com/ayeteadoe/ladybird/tree/webgpu-metal-genesis
- Had some experimentation with using Swift with our WIP C++ interop infrastructure for invoking Metal APIs instead of Obj-C
- Draft branch for initial WebGPU D3D12 backend: https://github.com/ayeteadoe/ladybird/commits/webgpu-directx-genesis/
- The WGSL compiler did have HLSL support, but I unfortunately lost it when rebasing some things around (originally all backends were combined into the initial Draft PR/branch)
| Pros | Cons |
|---|---|
| The native GPU API implementation can use Ladybird's AK/LibCore/LibGfx infrastructure to allow for much cleaner integration into LibWeb/WebGPU. | Heavy maintenance burden. For WebGPU, we'd need to implement a compiler for WGSL and have at least a Vulkan and Metal backend (potentially D3D12 for Windows) supported in the native GPU API. This will be a lot of code up front, but also when specs inevitably change there will be lots of moving parts to keep in sync. |
| Less third party dependencies to manage, as third party libraries may pull in a bunch of things we don't actually use outside of the main library. | In order to ensure spec conformance, we'd want a way to run the WebGPU Conformance Test Suite in some non local machine/automated way. That'd required GPU-enabled CI machines as we'd want to run all backends directly to test properly. |
Approach 2: Google's Dawn WebGPU library
NOTE: While the initial PR was complete, the follow-up branches listed here were all very rough and incomplete, never making it out of draft stage with lots of FIXMEs
- Completed PR/branch for initial WebGPU initialization: https://github.com/LadybirdBrowser/ladybird/pull/5783
- Draft branch for basic GPUCanvasContext and some stray APIs, allowing for clearing the canvas with a specific colour: https://github.com/ayeteadoe/ladybird/tree/webgpu-renderpass
- Draft branch for basic render pipeline with hardcoded shaders: https://github.com/ayeteadoe/ladybird/tree/webgpu-renderpipeline
-
Demo posted to the
#graphicschannel
-
Demo posted to the
| Pros | Cons |
|---|---|
| Much less of a maintenance burden as the focus is simply wrapping the existing library in LibWeb/WebGPU. | Unlike our usage of Google's skia and angle graphics libraries, dawn actually implements the WGSL and WebGPU specifications. Given WGSL/WebGPU is still only a candidate standard and is very early days of adoption, having a fully independent implementation does seem to be in the spirit of Ladybird's goals. Otherwise we are relying on third party dependencies to add support for web functionality and will have less ability to find and (temporarily) workaround spec issues or influence the spec altogether. |
Future Approaches
- See the first couple comments in this issue for cobtext, but the next approach I am going to try is blending the first 2 together. That is, create a Native GPU API abstraction layer where the first backend supported is Dawn. This allows us to implement other backends in the future to prevent tying us explicitly to Dawn. This approach will be done in LibWeb directly instead of in a lower level library, as that will make implementing the async APIs much simpler/cleaner.
I would suggest starting with approach 2, then building DIY to replace it. This way, it will be very easy to go back to Dawn if the DIY route ever becomes too complicated. Further, familiarity with Dawn will be very useful for creating unit tests that verify DIY matches it. And if they don't match, we can peep Dawn and see what they did.
Ah yeah, an approach I could see would be the first backend that LibWebGPU or LibGfx/WebGPU (not sure which location is ideal) would implement would be Dawn. Then Metal/Vulkan backends could be added later if desired. This is essentially what skia does for their new graphite API. LibWGSL would only be needed for the DIY Metal/Vulkan backends, so that could also be left out initially.
I am Interested in contributing to this issue/feature. @ayeteadoe would you mind if i work on this?.
@sameershaik Might want to wait for some confirmation/interest from maintainers on a direction, as regardless of approach there is a large effort ahead here so it'd be unfortunate to invest time in the wrong direction.
If they prefer the approach with a Dawn backend, then I may as well just re-open a PR with https://github.com/ayeteadoe/ladybird/tree/webgpu-dawn as that already exposes and tested the basic WebGPU initialization. If they prefer the DIY approach, then there will be a lot more to do where others could definitely help contribute.
Okay @ayeteadoe.
I've started a third approach that merges ideas from both initial approaches together, initially implementing a Dawn backend but ensures we're not inherently tied to/dependent on Dawn in the long term if we want Metal/Vulkan/other backends in the future.
See https://github.com/ayeteadoe/ladybird/tree/webgpu-staging for the working branch. The first several commits are relatively clean & faithful spec implementations, but after that things are mostly still WIP and just bare minimum to sanity test functionality.
The current state is that on all platforms we can do the basic initialization, and currently only on macOS we support the bare minimum IDL to get an extremely simple render passes.
Demos
Render Pass Clear
webgpu-renderpass.html
<!DOCTYPE html>
<html>
<head>
<title>Ladybird WebGPU: Render Pass (Clear)</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<canvas id="webgpuCanvas"></canvas>
<script>
const ctx = webgpuCanvas.getContext("webgpu");
let device;
let greenValue = 0.0;
function render() {
greenValue += 0.01;
if (greenValue > 1.0) {
greenValue = 0;
}
const renderPassDescriptor = {
colorAttachments: [
{
view: ctx.getCurrentTexture().createView(),
clearValue: {r: 1.0, g: greenValue, b: 0.0, a: 1.0},
loadOp: "clear",
storeOp: "store",
},
],
};
const commandEncoder = device.createCommandEncoder();
const renderPassEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
renderPassEncoder.end();
device.queue.submit([commandEncoder.finish()]);
requestAnimationFrame(render);
}
async function initWebGPU() {
if (!navigator.gpu) {
throw Error("WebGPU not supported");
}
const adapter = await navigator.gpu.requestAdapter();
device = await adapter.requestDevice();
ctx.configure({
device: device,
format: navigator.gpu.getPreferredCanvasFormat(),
});
requestAnimationFrame(render);
}
initWebGPU();
</script>
</body>
</html>
https://github.com/user-attachments/assets/a3c9ac00-563a-40a6-a17d-e0b92b8cd724
RenderPass Triangle
https://webgpu.github.io/webgpu-samples/?sample=helloTriangle
https://github.com/user-attachments/assets/5fbd895b-846a-427f-8176-441ee8d6406a