WebGPU is the Future of Graphics Development, in D as Well
WebGPU, as you might already know, is a future Web standard for low-level graphics and compute API. It will allow browser-based apps to interact with GPU in a modern way, using explicitly defined pipelines and an immutable state. WebGPU can be seen as a unification of Vulkan, Metal and Direct3D 12, which makes it a first truly cross-platform graphics API that doesn’t sacrifice performance for the sake of versatility and usability.
During the last year I’ve been following the development of wgpu-native, an Open Source implementation of WebGPU written in Rust. I’m excited by the fact that, despite being an API for the Web in the first place, it provides a C interface which makes it possible to use WebGPU with your favourite language — in my case, it’s of course D.
You wonder, why would anyone want to use WebGPU for native graphics programming when there are many well-established choices like OpenGL 4, bgfx and a plenty of high-level 3D engines? And if you want to squeeze maximum out of the GPU, why not use Vulkan directly? After 10 years of learning graphics tech and writing game engines, I came to the following conclusions.
- Platform and vendor independence matter almost equally much. You want your code to be portable seamlessly across operating systems, and you don’t want to wait for a new version of a high-level engine (that would probably also break your code) each time when you run into a bug or want to use functionality that otherwise could be added in a few minutes. Your own engine based on a standard graphics API gives you such flexibility. So directly using OpenGL is better that using a ready engine. The sad part is, OpenGL is not supported equally well on all platforms. It is also getting old, and it’s obvious that an “OpenGL way” of writing graphics code is currently at its dead end.
- Vulkan may seem as a replacement for OpenGL, but actually it’s not. Due to the specificity of Vulkan’s architecture, it can’t be just “added” to an existing single-API engine. OpenGL functionality doesn’t map one-to-one to Vulkan. The way to go is to use a mid-level graphics library with API-specific code at the backend. Any big commercial engine already does it, but until recently there was no widely accepted standard for a middleware like that. And guess what — WebGPU has full potential to become such a standard!
I’d like to quote Dzmitry Malyshau aka kvark who works at Mozilla and builds wgpu:
Today’s engines are pretty good! They are powerful and cheap to use. But I think, a lot of appeal to them was driven by the lack of proper solutions in the space below. Dealing with different graphics APIs on platforms is hard. Leaving it to a third-party library means putting a lot of trust in it.
I can see WebGPU on native being a go-to choice for amateur developers, students, indie professionals, mobile game studios, and many other groups. It could be the default GPU API, if it can deliver on its promises of safety, performance, and portability. We have a lot of interest and early adopters, as well as big forces in motion to make this real.
In my opinion, D and WebGPU make a great pair. The API feels just right in a language that supports C-like constructs such as pointers and POD structures, but also higher-level features like dynamic arrays and RAII. Initializing WebGPU descriptors in D looks not much different from JavaScript:
WGPURenderPipelineDescriptor rpDesc = {
label: “Render pipeline”,
layout: pipelineLayout,
vertex: {
modul: shaderModule,
entryPoint: “vs_main”,
bufferCount: 0,
buffers: null
},
primitive: {
topology: WGPUPrimitiveTopology.TriangleList,
stripIndexFormat: WGPUIndexFormat.Undefined,
frontFace: WGPUFrontFace.CCW,
cullMode: WGPUCullMode.None
},
multisample: {
count: 1,
mask: ~0,
alphaToCoverageEnabled: false
},
fragment: &fs,
depthStencil: null
};
Even OpenGL feels less “D-ish” than this, dare I say. And if you want something more coincise, you can wrap WebGPU idioms into D classes, which you are going to do anyway when writing a frontend for your engine.
By the way, D implementation of a triangle demo in WebGPU is about 400 lines of code, and Vulkan equivalent is 4–5 times longer.
Aside from wgpu-native, there is another major WebGPU implementation, Google’s Dawn. In my opinion, the most important milestone that will accelerate adoption of WebGPU in native programming is full interface unification between implementations. wgpu-native is rapidly moving towards a goal of using a shared header file with Dawn, which is a great thing. I’m using wgpu-native with D using my binding, bindbc-wgpu. It is based on BindBC loader that allows to write cross-platform dynamic bindings for C libraries. The downside of maintaining such an interlayer is that you have to manually rewrite large parts of it every time when major changes are made in target API. You have to read the header file and carefully translate all types and function signatures to your language. This task could be easily automated if the API was provided not only in a form of a C header file, but also in some parsing-friendly data interchange format like JSON. A unified header means that all changes to the API will be streamlined, and there are chances to get a “webgpu.json” of some sort in the future. And again D shines here because of its powerful metaprogramming machinery that allows to generate code at compile time. D allows to write a binding that generates itself!
Another interesting things are SPIR-V and its textual counterpart WGSL that is actively developed right now. Thanks to Khronos’ LLVM SPIR-V backend, it is possible — theoretically, at least — to compile existing languages to SPIR-V. Shaders in D? I’m optimistic about that.