mcc

glitch girl

Avatar by @girlfiend

Also on Mastodon.


WebGPU is the new WebGL. That means it is the new way to draw 3D in web browsers. It is, in my opinion, very good actually. It is so good I think it will also replace Canvas and become the new way to draw 2D in web browsers. In fact it is so good I think it will replace Vulkan as well as normal OpenGL, and become just the standard way to draw, in any kind of software, from any programming language. This is pretty exciting to me. WebGPU is a little bit irritating— but only a little bit, and it is massively less irritating than any of the things it replaces.

WebGPU goes live… today, actually. Chrome 113 shipped in the final minutes of me finishing this post and should be available in the "About Chrome" dialog right this second. If you click here, and you see a rainbow triangle, your web browser has WebGPU. By the end of the year WebGPU will be everywhere, in every browser. (All of this refers to desktop computers. On phones, it won't be in Chrome until later this year; and Apple I don't know. Maybe one additional year after that.)

If you are not a programmer, this probably doesn't affect you. It might get us closer to a world where you can just play games in your web browser as a normal thing like you used to be able to with Flash. But probably not because WebGL wasn't the only problem there.

If you are a programmer, let me tell you what I think this means for you.

Sections below:

  • A history of graphics APIs (You can skip this)
  • What's it like?
  • How do I use it?
    • Typescript / NPM world
    • I don't know what a NPM is I Just wanna write CSS and my stupid little script tags
    • Rust / C++ / Posthuman Intersecting Tetrahedron

A history of graphics APIs (You can skip this)

Yo! Yogi
1991
Back in the dawn of time there were two ways to make 3D on a computer: You did a bunch of math; or you bought an SGI machine. SGI were the first people who were designing circuitry to do the rendering parts of a 3D engine for you. They had this C API for describing your 3D models to the hardware. At some point it became clear that people were going to start making plugin cards for regular desktop computers that could do the same acceleration as SGI's big UNIX boxes, so SGI released a public version of their API so it would be possible to write code that would work both on the UNIX boxes and on the hypothetical future PC cards. This was OpenGL. `color()` and `rectf()` in IRIS GL became `glColor()` and `glRectf()` in OpenGL.

"Waterfalls" by TLC
1995
When the PC 3D cards actually became a real thing you could buy, things got real messy for a bit. Instead of signing on with OpenGL Microsoft had decided to develop their own thing (Direct3D) and some of the 3D card vendors also developed their own API standards, so for a while certain games were only accelerated on certain graphics cards and people writing games had to write their 3D pipelines like four times, once as a software renderer and a separate one for each card type they wanted to support. My perception is it was Direct3D, not OpenGL, which eventually managed to wrangle all of this into a standard, which really sucked if you were using a non-Microsoft OS at the time. It really seemed like DirectX (and the "X Box" standalone console it spawned) were an attempt to lock game companies into Microsoft OSes by getting them to wire Microsoft exclusivity into their code at the lowest level, and for a while it really worked.

Shrek
2000
It is the case though it wasn't very long into the Direct3D lifecycle before you started hearing from Direct3D users that it was much, much nicer to use than OpenGL, and OpenGL quickly got to a point where it was literally years behind Direct3D in terms of implementing critical early features like shaders, because the Architecture Review Board of card vendors that defined OpenGL would spend forever bickering over details whereas Microsoft could just implement stuff and expect the card vendor to work it out.

Let's talk about shaders. The original OpenGL was a "fixed function renderer", meaning someone had written down the steps in a 3D renderer and it performed those steps in order.

[API] → Primitive Processing → (1) Transform and Lighting → Primitive Assembly → Rasterizer → (2) Texture Environment → (2) Color sum → (2) Fog → (2) Alpha Test → Depth/Stencil → Color-buffer Blend → Dither → [Frame Buffer]
Modified Khronos Group image

Each box in the "pipeline" had some dials on the side so you could configure how each feature behaved, but you were pretty much limited to the features the card vendor gave you. If you had shadows, or fog, it was because OpenGL or an extension had exposed a feature for drawing shadows or fog. What if you want some other feature the ARB didn't think of, or want to do shadows or fog in a unique way that makes your game look different from other games? Sucks to be you. This was obnoxious, so eventually "programmable shaders" were introduced. Notice some of the boxes above are yellow? Those boxes became replaceable. The (1) boxes got collapsed into the "Vertex Shader", and the (2) boxes became the "Fragment Shader"². The software would upload a computer program in a simple C-like language (upload the actual text of the program, you weren't expected to compile it like a normal program)³ into the video driver at runtime, and the driver would convert that into configurations of ALUs (or whatever the card was actually doing on the inside) and your program would become that chunk of the pipeline. This opened things up a lot, but more importantly it set card design on a kinda strange path. Suddenly video cards weren't specialized rendering tools anymore. They ran software.

Time Magazine, "What kind of President would John Kerry be?"
2004
Pretty shortly after this was another change. Handheld devices were starting to get to the point it made sense to do 3D rendering on them (or at least, to do 2D compositing using 3D video card hardware like desktop machines had started doing). DirectX was never in the running for these applications. But implementing OpenGL on mid-00s mobile silicon was rough. OpenGL was kind of… large, at this point. It had all these leftover functions from the SGI IRIX era, and then it had this new shiny OpenGL 2.0 way of doing things with the shaders and everything and not only did this mean you basically had two unrelated APIs sitting side by side in the same API, but also a lot of the OpenGL 1.x features were traps. The spec said that every video card had to support every OpenGL feature, but it didn't say it had to support them in Hardware, so there were certain early-90s features that 00s card vendors had decided nobody really uses, and so if you used those features the driver would render the screen, copy the entire screen into regular RAM, perform the feature on the CPU and then copy the results back to the video card. Accidentally activating one of these trap features could easily move you from 60 FPS to 1 FPS. All this legacy baggage promised a lot of extra work for the manufacturers of the new mobile GPUs, so to make it easier Khronos (which is what the ARB had become by this point) introduced an OpenGL "ES", which stripped out everything except the features you absolutely needed. Instead of being able to call a function for each polygon or each vertex you had to use the newer API of giving OpenGL a list of coordinates in a block in memory⁴, you had to use either the fixed function or the shader pipeline with no mixing (depending on whether you were using ES 1.x or ES 2.x), etc. This partially made things simpler for programmers, and partially prompted some annoying rewrites. But as with shaders, what's most important is the long-term strange-ing this change presaged: Starting at this point, the decisions of Khronos increasingly were driven entirely by the needs and wants of hardware manufacturers, not programmers.

The Apple iPhone
2008
With OpenGL ES devices in the world, OpenGL started to graduate from being "that other graphics API that exists, I guess" and actually take off. The iPhone, which used OpenGL ES, gave a solid mass-market reason to learn and use OpenGL. Nintendo consoles started to use OpenGL or something like it. OpenGL had more or less caught up with DirectX in features, especially if you were willing to use extensions. Browser vendors, in that spurt of weird hubris that gave us the original WebAudio API, adapted OpenGL ES into JavaScript as "WebGL", which makes no sense because as mentioned OpenGL ES was all about packing bytes into arrays full of geometry and JavaScript doesn't have direct memory access or even integers, but they added packed binary arrays to the language and did it anyway. So with all this activity, sounds like things are going great, right?

Steven Universe
2013
No! Everything was terrible! As it matured, OpenGL fractured into a variety of slightly different standards with varying degrees of cross-compatibility. OpenGL ES 2.0 was the same as OpenGL 3.3, somehow. WebGL 2.0 is very almost OpenGL ES 3.0 but not quite. Every attempt to resolve OpenGL's remaining early mistakes seemed to wind up duplicating the entire API as new functions with slightly different names and slightly different signatures. A big usability issue with OpenGL was even after the 2.0 rework it had a lot of shared global state, but the add-on systems that were supposed to resolve this (VAOs and VBOs) only wound up being even more global state you had to keep track of. A big trend in the 10s was "GPGPU" (General Purpose GPU); programmers started to realize that graphics cards worked as well as, but were slightly easier to program than, a CPU's vector units, so they just started accelerating random non-graphics programs by doing horrible hacks like stuffing them in pixel shaders and reading back a texture containing an encoded result. Before finally resolving on compute shaders (in other words: before giving up and copying DirectX's solution), Khronos's original steps toward actually catering to this were either poorly adopted (OpenCL) or just plain bad ideas (geometry shaders). It all built up. Just like in the pre-ES era, OpenGL had basically become several unrelated APIs sitting in the same header file, some of which only worked on some machines. Worse, nothing worked quite as well as you wanted it to; different video card vendors botched the complexity, implementing features slightly differently (especially tragically, implementing slightly different versions of the shader language) or just badly, especially in the infamously bad Windows OpenGL drivers.

The way out came from, this is how I see it anyway, a short-lived idea called "AZDO". This technically consisted of a single GDC talk⁵, and I have no reason to believe the GDC talk originated the idea, but what the talk did do is give a name to the idea that underlies Vulkan, DirectX 12, and Metal. "Approaching Zero Driver Overhead". Here is the idea: By 2015 video cards had pretty much standardized on a particular way of working and that way was known and that way wasn't expected to change for ten years at least. Graphics APIs were originally designed around the functionality they exposed, but that functionality hadn't been a 1:1 map to how GPUs look on the inside for ten years at least. Drivers had become complex beasts that rather than just doing what you told them tried to intuit what you were trying to do and then do that in the most optimized way, but often they guessed wrong, leaving software authors in the ugly position of trying to intuit what the driver would intuit in any one scenario. AZDO was about threading your way through the needle of the graphics API in such a way your function calls happened to align precisely with what the hardware was actually doing, such that the driver had nothing to do and stuff just happened.

Star Wars: The Force Awakens
2016
Or we could just design the graphics API to be AZDO from the start. That's Vulkan. (And DirectX 12, and Metal.) The modern generation of graphics APIs are about basically throwing out the driver, or rather, letting your program be the driver. The API primitives map directly to GPU internal functionality⁶, and the GPU does what you ask without second guessing. This gives you an incredible amount of power and control. Remember that "pipeline" diagram up top? The modern APIs let you define "pipeline objects"; while graphics shaders let you replace boxes within the diagram, and compute shaders let you replace the diagram with one big shader program, pipeline objects let you draw your own diagram. You decide what blocks of GPU memory are the sources, and which are the destinations, and how they are interpreted, and what the GPU does with them, and what shaders get called. All the old sources of confusion get resolved. State is bound up in neatly defined objects instead of being global. Card vendors always designed their shader compilers different, so we'll replace the textual shader language with a bytecode format that's unambiguous to implement and easier to write compilers for. Vulkan goes so far as to allow⁷ you to write your own allocator/deallocator for GPU memory.

So this is all very cool. There is only one problem, which is that with all this fine-grained complexity, Vulkan winds up being basically impossible for humans to write. Actually, that's not really fair. DX12 and Metal offer more or less the same degree of fine-grained complexity, and by all accounts they're not so bad to write. The actual problem is that Vulkan is not designed for humans to write. Literally. Khronos does not want you to write Vulkan, or rather, they don't want you to write it directly. I was in the room when Vulkan was announced, across the street from GDC in 2015, and what they explained to our faces was that game developers were increasingly not actually targeting the gaming API itself, but rather targeting high-level middleware, Unity or Unreal or whatever, and so Vulkan was an API designed for writing middleware. The middleware developers were also in the room at the time, the Unity and Epic and Valve guys. They were beaming as the Khronos guy explained this. Their lives were about to get much, much easier.

My life was about to get harder. Vulkan is weird— but it's weird in a way that makes a certain sort of horrifying machine sense. Every Vulkan call involves passing in one or two huge structures which are themselves a forest of other huge structures, and every structure and sub-structure begins with a little protocol header explaining what it is and how big it is. Before you allocate memory you have to fill out a structure to get back a structure that tells you what structure you're supposed to structure your memory allocation request in. None of it makes any sense— unless you've designed a programming language before, in which case everything you're reading jumps out to you as "oh, this is contrived like this because it's designed to be easy to bind to from languages with weird memory-management techniques" "this is a way of designing a forward-compatible ABI while making no assumptions about programming language" etc. The docs are written in a sort of alien English that fosters no understanding— but it's also written exactly the way a hardware implementor would want in order to remove all ambiguity about what a function call does. In short, Vulkan is not for you. It is a byzantine contract between hardware manufacturers and middleware providers, and people like… well, me, are just not part of the transaction.

Khronos did not forget about you and me. They just made a judgement, and this actually does make a sort of sense, that they were never going to design the perfectly ergonomic developer API anyway, so it would be better to not even try and instead make it as easy as possible for the perfectly ergonomic API to be written on top, as a library. Khronos thought within a few years of Vulkan⁸ being released there would be a bunch of high-quality open source wrapper libraries that people would use instead of Vulkan directly. These libraries basically did not materialize. It turns out writing software is work and open source projects do not materialize just because people would like them to⁹.

Star Wars: The Rise of Skywalker
2019
This leads us to the other problem, the one Vulkan developed after the fact. The Apple problem. The theory on Vulkan was it would change the balance of power where Microsoft continually released a high-quality cutting-edge graphics API and OpenGL was the sloppy open-source catch up. Instead, the GPU vendors themselves would provide the API, and Vulkan would be the universal standard while DirectX would be reduced to a platform-specific oddity. But then Apple said no. Apple (who had already launched their own thing, Metal) announced not only would they never support Vulkan, they would not support OpenGL, anymore¹⁰. From my perspective, this is just DirectX again; the dominant OS vendor of our era, as Microsoft was in the 90s, is pushing proprietary graphics tech to foster developer lock-in. But from Apple's perspective it probably looks like— well, the way DirectX probably looked from Microsoft's perspective in the 90s. They're ignoring the jagged-metal thing from the hardware vendors and shipping something their developers will actually want to use.

With Apple out, the scene looked different. Suddenly there was a next-gen API for Windows, a next-gen API for Mac/iPhone, and a next-gen API for Linux/Android. Except Linux has a severe driver problem with Vulkan and a lot of the Linux devices I've been checking out don't support Vulkan even now after it's been out seven years. So really the only platform where Vulkan runs natively is Android. This isn't that bad. Vulkan does work on Windows and there are mostly no problems, though people who have the resources to write a DX12 backend seem to prefer doing so. The entire point of these APIs is that they're flyweight things resting very lightly on top of the hardware layer, which means they aren't really that different, to the extent that a Vulkan-on-Metal emulation layer named MoltenVK exists and reportedly adds almost no overhead. But if you're an open source kind of person who doesn't have the resources to pay three separate people to write vaguely-similar platform backends, this isn't great. Writing Vulkan your code can technically run on all platforms, but you're writing in the least pleasant of the three APIs to work with and you get the advantage of using a true-native API on neither of the two major platforms. You might even have an easier time just writing DX12 and Metal and forgetting Vulkan (and Android) altogether. In short, Vulkan solves all of OpenGL's problems at the cost of making something that no one wants to use and no one has a reason to use.

The way out turned out to be something called ANGLE. Let me back up a bit.

Super Meat Boy
2010, again
WebGL was designed around OpenGL ES. But it was never exactly the same as OpenGL ES, and also technically OpenGL ES never really ran on desktops, and also regular OpenGL on desktops had Problems. So the browser people eventually realized that if you wanted to ship an OpenGL compatibility layer on Windows, it was actually easier to write an OpenGL emulator in DirectX than it was to use OpenGL directly and have to negotiate the various incompatibilities between OpenGL implementations of different video card drivers. The browser people also realized that if slight compatibility differences between different OpenGL drivers was hell, slight incompatibility differences between four different browsers times three OSes times different graphics card drivers would be the worst thing ever. From what I can only assume was desperation, the most successful example I've ever seen of true cross-company open source collaboration emerged: ANGLE, a BSD-licensed OpenGL emulator originally written by Google but with honest-to-goodness contributions from both Firefox and Apple, which is used for WebGL support in literally every web browser.

But nobody actually wants to use WebGL, right? We want a "modern" API, one of those AZDO thingies. So a W3C working group sat down to make Web Vulkan, which they named WebGPU. I'm not sure my perception of events is to be trusted, but my perception of how this went from afar was that Apple was the most demanding participant in the working group, and also the participant everyone would naturally by this point be most afraid of just spiking the entire endeavor, so reportedly Apple just got absolutely everything they asked for and WebGPU really looks a lot like Metal. But Metal was always reportedly the nicest of the three modern graphics APIs to use, so that's… good? Encouraged by the success with ANGLE (which by this point was starting to see use as a standalone library in non-web apps¹¹), and mindful people would want to use this new API with WebASM, they took the step of defining the standard simultaneously as a JavaScript IDL and a C header file, so non-browser apps could use it as a library.

WGPU
2023
WebGPU is the child of ANGLE and Metal. WebGPU is the missing open-source "ergonomic layer" for Vulkan. WebGPU is in the web browser, and Microsoft and Apple are on the browser standards committee, so they're "bought in", not only does WebGPU work good-as-native on their platforms but anything WebGPU can do will remain perpetually feasible on their OSes regardless of future developer lock-in efforts. (You don't have to worry about feature drift like we're already seeing with MoltenVK.) WebGPU will be on day one (today) available with perfectly equal compatibility for JavaScript/TypeScript (because it was designed for JavaScript in the first place), for C++ (because the Chrome implementation is in C, and it's open source) and for Rust (because the Firefox implementation is in Rust, and it's open source).

I feel like WebGPU is what I've been waiting for this entire time.


What's it like?

I can't compare to DirectX or Metal, as I've personally used neither. But especially compared to OpenGL and Vulkan, I find WebGPU really refreshing to use. I have tried, really tried, to write Vulkan, and been defeated by the complexity each time. By contrast WebGPU does a good job of adding complexity only when the complexity adds something. There are a lot of different objects to keep track of, especially during initialization (see below), but every object represents some Real Thing that I don't think you could eliminate from the API without taking away a useful ability. (And there is at least the nice property that you can stuff all the complexity into init time and make the process of actually drawing a frame very terse.) WebGPU caters to the kind of person who thinks it might be fun to write their own raymarcher, without requiring every programmer to be the kind of person who thinks it would be fun to write their own implementation of malloc.

The Problems

There are three Problems. I will summarize them thusly:

  • Text
  • Lines
  • The Abomination

Text and lines are basically the same problem. WebGPU kind of doesn't… have them. It can draw lines, but they're only really for debugging– single-pixel width and you don't have control over antialiasing. So if you want a "normal looking" line you're going to be doing some complicated stuff with small bespoke meshes and an SDF shader. Similarly with text, you will be getting no assistance– you will be parsing OTF font files yourself and writing your own MSDF shader, or more likely finding a library that does text for you.

This (no lines or text unless you implement it yourself) is a totally normal situation for a low-level graphics API, but it's a little annoying to me because the web browser already has a sophisticated anti-aliased line renderer (the original Canvas API) and the most advanced text renderer in the world. (There is some way to render text into a Canvas API texture and then transfer the Canvas contents into WebGPU as a texture, which should help for some purposes.)

Then there's WGSL, or as I think of it, The Abomination. You will probably not be as annoyed by this as I am. Basically: One of the benefits of Vulkan is that you aren't required to use a particular shader language. OpenGL uses GLSL, DirectX uses HLSL. Vulkan used a bytecode, called SPIR-V, so you could target it from any shader language you wanted. WebGPU was going to use SPIR-V, but then Apple said no¹². So now WebGPU uses WGSL, a new thing developed just for WebGPU, as its only shader language. As far as shader languages go, it is fine. Maybe it is even good. I'm sure it's better than GLSL. For pure JavaScript users, it's probably objectively an improvement to be able to upload shaders as text files instead of having to compile to bytecode. But gosh, it would have been nice to have that choice! (The "desktop" versions of WebGPU still keep SPIR-V as an option.)


How do I use it?

You have three choices for using WebGPU: Use it in JavaScript in the browser, use it in Rust/C++ in WebASM inside the browser, or use it in Rust/C++ in a standalone app. The Rust/C++ APIs are as close to the JavaScript version as language differences will allow; the in-browser/out-of-browser APIs for Rust and C++ are identical (except for standalone-specific features like SPIR-V). In standalone apps you embed the WebGPU components from Chrome or Firefox as a library; your code doesn't need to know if the WebGPU library is a real library or if it's just routing through your calls to the browser.

Regardless of language, the official WebGPU spec document on w3.org is a clear, readable reference guide to the language, suitable for just reading in a way standard specifications sometimes aren't. (I haven't spent as much time looking at the WGSL spec but it seems about the same.) If you get lost while writing WebGPU, I really do recommend checking the spec.

Most of the "work" in WebGPU, other than writing shaders, consists of the construction (when your program/scene first boots) of one or more "pipeline" objects, one per "pass", which describe "what shaders am I running, and what kind of data can get fed into them?"¹³. You can chain pipelines end-to-end within a queue: have a compute pass generate a vertex buffer, have a render pass render into a texture, do a final render pass which renders the computed vertices with the rendered texture.

Here, in diagram form, are all the things you need to create to initially set up WebGPU and then draw a frame. This might look a little overwhelming. Don't worry about it! In practice you're just going to be copying and pasting a big block of boilerplate from some sample code. However at some point you're going to need to go back and change that copypasted boilerplate, and then you'll want to come back and look up what the difference between any of these objects is.

At init:
Context: One <canvas> or window. Exists at boot.

WebGPU instance: navigator.gpu. Exists at boot.

Adapter: If there’s more than one video card, you can pick one. Feed this to Canvas Configuration. Vends a Device. Vends a Queue.

Canvas Configuration: You make this. Feed to Context.

Queue: Executes work batches in order. You’ll use this later.

Device: An open connection to the adapter. Gives color format to the Canvas Configuration. Vends Buffers, Textures, and Pipelines and compiles code to Shaders.

Buffer: A chunk of GPU memory. You’ll use this later.

Texture:GPU memory formatted as an image. You’ll use this later.

Shader: Vertex, Fragment, or Compute program. Feed to Pipeline.

Buffer Layout: Describes how to interpret bytes in a Buffer. Like a C Struct definition. Describes a Buffer. Feed to Pipeline.

Vertex Layout: Buffer layout specialized for meshes/triangle lists. Describes a Buffer. Feed to Pipeline.
For each frame:
Step one:
Take a Buffer which you wish to update this frame. This will vend a Mapped Range, which is a Typed array that can read/write data from part of a GPU buffer. When you "unmap" the mapped range, the changes are automatically synchronized with the appropriate queue at that moment
Step two:
Device vends a Command Encoder.
Context vends the Current Texture for this frame. Feed this to the Command Encoder and get a Render Pass. (The Command Encoder can also vend Compute Passes.
Feed Viewport and Scissor rects (these are just numbers) to the Render Pass. Feed a Pipeline to the scissor rect. Feed Buffers (uniforms, vertices, indices) to the Render Pass. Feed Textures (inputs to shaders) to the Render Pass.
Feed Render Passes and Compute Passes to the Queue.

Some observations in no particular order:

  • When describing a "mesh" (a 3D model to draw), a "vertex" buffer is the list of points in space, and the "index" is an optional buffer containing the order in which to draw the points. Not sure if you knew that.
  • Right now the "queue" object seems a little pointless because there's only ever one global queue. But someday WebGPU will add threading and then there might be more than one.
  • A command encoder can only be working on one pass at a time; you have to mark one pass as complete before you request the next one. But you can make more than one command encoder and submit them all to the queue at once.
  • Back in OpenGL when you wanted to set a uniform, attribute, or texture on a shader, you did it by name. In WebGPU you have to assign these things numbers in the shader and you address them by number.¹⁴
  • Although textures and buffers are two different things, you can instruct the GPU to just turn a texture into a buffer or vice versa.
  • I do not list "pipeline layout" or "bind group layout" objects above because I honestly don't understand what they do. I've only ever set them to default/blank.
  • In the Rust API, a "Context" is called a "Surface". I don't know if there's a difference.

Getting a little more platform-specific:

TypeScript / NPM world

The best way to learn WebGPU for TypeScript I know is Alain Galvin's "Raw WebGPU" tutorial. It is a little friendlier to someone who hasn't used a low-level graphics API before than my sandbag introduction above, and it has a list of further resources at the end.

Since code snippets don't get you something runnable, Alain's tutorial links a completed source repo with the tutorial code, and also I have a sample repo which is based on Alain's tutorial code and adds simple animation as well as Preact¹⁵. Both my and Alain's examples use NPM and WebPack¹⁶.

If you don't like TypeScript: I would recommend using TypeScript anyway for WGPU. You don't actually have to add types to anything except your WGPU calls, you can type everything "any". But building that pipeline object involves big trees of descriptors containing other descriptors, and it's all just plain JavaScript dictionaries, which is nice, until you misspell a key, or forget a key, or accidentally pass the GPUPrimitiveState table where it wanted the GPUVertexState table. Your choices are to let TypeScript tell you what errors you made, or be forced to reload over and over watching things break one at a time.

I don't know what a NPM is I Just wanna write CSS and my stupid little script tags

If you're writing simple JS embedded in web pages rather than joining the NPM hivemind, honestly you might be happier using something like three.js¹⁷ in the first place, instead of putting up with WebGPU's (relatively speaking) hyper-low-level verbosity. You can include three.js directly in a script tag using existing CDNs (although I would recommend putting in a subresource SHA hash to protect yourself from the CDN going rogue).

But! If you want to use WebGPU, Alain Galvin's tutorial, or renderer.ts from his sample code, still gets you what you want. Just go through and anytime there's a little : GPUBlah wart on a variable delete it and the TypeScript is now JavaScript. And as I've said, the complexity of WebGPU is mostly in pipeline init. So I could imagine writing a single <script> that sets up a pipeline object that is good for various purposes, and then including that script in a bunch of small pages that each import¹⁸ the pipeline, feed some floats into a buffer mapped range, and draw. You could do the whole client page in like ten lines probably.

Rust

So as I've mentioned, one of the most exciting things about WebGPU to me is you can seamlessly cross-compile code that uses it without changes for either a browser or for desktop. The desktop code uses library-ized versions of the actual browser implementations so there is low chance of behavior divergence. If "include part of a browser in your app" makes you think you're setting up for a code-bloated headache, not in this case; I was able to get my Rust "Hello World" down to 3.3 MB, which isn't much worse than SDL, without even trying. (The browser hello world is like 250k plus a 50k autogenerated loader, again before I've done any serious minification work.)

If you want to write WebGPU in Rust¹⁹, I'd recommend checking out this official tutorial from the wgpu project, or the examples in the wgpu source repo. As of this writing, it's actually a lot easier to use Rust WebGPU on desktop than in browser; the libraries seem to mostly work fine on web, but the Rust-to-wasm build experience is still a bit rough. I did find a pretty good tutorial for wasm-pack here²⁰. However most Rust-on-web developers seem to use (and love) something called "Trunk". I haven't used Trunk yet but it replaces wasm-pack as a frontend, and seems to address all the specific frustrations I had with wasm-pack.

I do have also a sample Rust repo I made for WebGPU, since the examples in the wgpu repo don't come with build scripts. My sample repo is very basic²¹ and is just the "hello-triangle" sample from the wgpu project but with a Cargo.toml added. It does come with working single-line build instructions for web, and when run on desktop with --release it minimizes disk usage. (It also prints an error message when run on web without WebGPU, which the wgpu sample doesn't.) You can see this sample's compiled form running in a browser here.

C++

If you're using C++, the library you want to use is called "Dawn". I haven't touched this but there's an excellently detailed-looking Dawn/C++ tutorial/intro here. Try that first.

Posthuman Intersecting Tetrahedron

I have strange, chaotic daydreams of the future. There's an experimental project called rust-gpu that can compile Rust to SPIR-V. SPIR-V to WGSL compilers already exist, so in principle it should already be possible to write WebGPU shaders in Rust, it's just a matter of writing build tooling that plugs the correct components together. (I do feel, and complained above, that the WGSL requirement creates a roadblock for use of alternate shader languages in dynamic languages, or languages like C++ with a broken or no build system— but Rust is pretty good at complex pre-build processing, so as long as you're not literally constructing shaders on the fly then probably it could make this easy.)

I imagine a pure-Rust program where certain functions are tagged as compile-to-shader, and I can share math helper functions between my shaders and my CPU code, or I can quickly toggle certain functions between "run this as a filter before writing to buffer" or "run this as a compute shader" depending on performance considerations and whim. I have an existing project that uses compute shaders and answering the question "would this be faster on the CPU, or in a compute shader?"²² involved writing all my code twice and then writing complex scaffold code to handle switching back and forth. That could have all been automatic. Could I make things even weirder than this? I like Rust for low-level engine code, but sometimes I'd prefer to be writing TypeScript for business logic/"game" code. In the browser I can already mix Rust and TypeScript, there's copious example code for that. Could I mix Rust and TypeScript on desktop too? If wgpu is already my graphics engine, I could shove in Servo or QuickJS or something, and write a cross-platform program that runs in browser as TypeScript with wasm-bindgen Rust embedded inside or runs on desktop as Rust with a TypeScript interpreter inside. Most Rust GUI/game libraries work in wasm already, and there's this pure Rust WebAudio implementation (it's currently not a drop-in replacement for wasm-bindgen WebAudio but that could be fixed). I imagine creating a tiny faux-web game engine that is all the benefits of Electron without any the downsides. Or I could just use Tauri for the same thing and that would work now without me doing any work at all.

Could I make it weirder than that? WebGPU's spec is available as a machine-parseable WebIDL file; would that make it unusually easy to generate bindings for, say, Lua? If I can compile Rust to WGSL and so write a pure-Rust-including-shaders program, could I compile TypeScript, or AssemblyScript or something, to WGSL and write a pure-TypeScript-including-shaders program? Or if what I care about is not having to write my program in two languages and not so much which language I'm writing, why not go the other way? Write an LLVM backend for WGSL, compile it to native+wasm and write an entire-program-including-shaders in WGSL. If the w3 thinks WGSL is supposed to be so great, then why not?

Okay that's my blog post.


¹ 113 or newer

² "Fragment" is OpenGL for "Pixel".

³ I am still trying to figure out whether modern video cards are simply based on the internal architecture of Quake 3.

⁴ And those coordinates HAD to describe triangles, now. Want to draw a rectangle? Fuck you, apparently!

⁵ (And a series of OpenGL techniques and extensions no one seems to have really got the chance to use before OpenGL was sunset.)

⁶ Why is a "push constant" different from a "uniform", in Vulkan/WebGPU? Well, because those are two different things inside of the GPU chip. Why would you use one rather than the other? Well, learn what the GPU chip is doing, and then you'll understand why either of these might be more appropriate in certain situations. Does this sound like a lot of mental overhead? Well, sometimes, but honestly, it's less mental overhead than trying to understand whatever "VAO"s were. (EDIT: It turns out WebGPU doesn't have push constants. I only thought they did because the Rust WebGPU library offers them as a desktop-specific extension. Anyway, it's an example.)

⁷ Require

⁸ By the way, have you noticed the cheesy Star Trek joke yet? The companies with seats on the Khronos board have a combined market capitalization of 6.1 trillion dollars. This is the sense of humor that 6.1 trillion dollars buys you.

⁹ There are decent Vulkan-based OSS game engines, though. LÖVR, the Lua-based game engine I use for my job, has a very nice pared-down Lua frontend on top of its Vulkan backend that is usable by beginners but exposes most of the GPU flexibility you actually care about. (The Lua API is also itself a thin wrapper atop a LÖVR-specific C API, and the graphics module is designed to be separable from LÖVR in principle, so if I didn't have WebGPU I'd actually probably be using LÖVR's C frontend even outside Lua now.)

¹⁰ This made OpenGL's fragmentation problem even worse, as the "final" form of OpenGL is basically version 4.4-4.6 somewheres, whereas Apple got to 4.1 and simply stopped. So if you want to release OpenGL software on a Mac, for however longer that's allowed, you are targeting something that is almost, but not quite, the final full-featured version of the API. This sucks! There is some important stuff in 4.3.

¹¹ Microsoft shipped ANGLE in Windows 11 as the OpenGL component of their Android compatibility layer, and ANGLE has also been shipped as the graphics engine in a small number of games such as, uh… [checking Wikipedia] Shovel Knight?! You might see it used more if ANGLE had been designed for library reuse from day one like WebGPU was, or if anyone wanted to use OpenGL.

¹² If I were a cynical, paranoid conspiracy theorist, I would float the theory here that Apple at some point decided they wanted to leave open the capability to sue the other video card developers on the Khronos board, so they are aggressively refusing to let their code touch anything that has touched the Vulkan patent pool to insulate themselves from counter-suits. Or that is what I would say if I were a cynical, paranoid conspiracy theorist. Hypothetically.

¹³ If you pay close attention here you'll notice something weird: Pipelines combine buffer interfaces with specific shaders, so you can use a single pipeline with many different buffers but only one shader or shader pair. What early users of both WebGPU and Vulkan have found is that you wind up needing a lot of pipeline objects in a fair-sized program, and although the pipeline objects themselves are lightweight, creating the pipeline objects can be kind of slow, especially if you have to create more than one of them on a single frame. So this is an identified pain point, having to think ahead to all the pipeline objects you'll need and cache them ahead of time, and Vulkan has already tried to address this by introducing something called "shader objects" like one month ago. Hopefully the WebGPU WG will look into doing something similar in the next revision.

¹⁴ This annoys me, but I've talked to people who like it better, I guess because they had problems with typo'ing their uniform names.

¹⁵ This sample is a little less complete than I hoped to have it by the time I posted this. Known problems as of this second: It comes with a Preact Canvas wrapper that enforces aspect ratio and integer-multiple size requirements for the canvas, but it doesn't have an option to run full screen; there are unnecessary scroll bars that appear if you open the sample in a non-WebGPU browser (and possibly under other circumstances as well); there is an unused file named "canvas2image.ts", which was supposed to be used to let you download the state as a PNG and ought to be either wired up or removed; if you do add canvas2image back in it doesn't work, and I don't know if the problem is at my end or Chrome's; the comments refer to some concepts from 2021 WebGPU, like swapchains.

¹⁶ If you don't like WebPack, that implies you know enough about JavaScript you already know how to replace the WebPack in the example with something else.

¹⁷ Not a specific three.js endorsement. I've never used it. People seem to like it. There (BabylonJS) are (RedGPU) alternatives (PlayCanvas, which by the way is incredibly cool).

¹⁸ Wait, do JS modules/import just work in browsers now? I don't even know lol

¹⁹ If you're using Rust, it's quite possible that you are using WebGPU already. The Rust library quickly got far ahead of its Firefox parent software and has for some time now already been adopted as the base graphics layer in emerging GUI libraries such as Iced. So you could maybe just use Iced or Bevy for high-level stuff and then do additional drawing in raw WebGPU. I haven't tried.

²⁰ Various warnings if you go this way: If you're on Windows I recommend installing the wasm-pack binary package instead of trying to install it through cargo. If you're making a web build from scratch instead of using my sample, note the slightly alarming "as of 2022-9-20" note here in the wgpu wiki.

²¹ This sample also has as of this writing some caveats: It can only fill the window, it can't do aspect ratios or integer-multiple restrictions; it has no animation; in order to get the fill-the-window behavior, I had to base it on a winit PR, so the version of winit used is a little older than it could be; there are outstanding warnings; I am unclear on the license status of the wgpu sample code I used, so until I can get clarification or rewrite it you should probably follow the wgpu MIT license even when using this sample on web. I plan to eventually expand this example to include controller support and sound.

²² Horrifyingly, the answer turned out to be "it depends on which device you're running on".


You must log in to comment.

in reply to @mcc's post:

Ive been working on a webgpu 2d light simulation thingy this week and its been SO SURPRISINGLY EASY! once i got the js stuff set up to let me throw some data at the compute shader and get it back, I was golden. It even let me pass it arrays of structs, which was frankly mindblowing because when I previously dealt with webgl things, it was much more strict about what I could and couldnt do. Webgpu is amazingly cool and im glad it exists

what makes esm in node a disaster? mostly asking cos i’ve run into esm not working in mode a few times and i would just give up until i managed to get it working on accident.

since the examples in the wgpu repo don't come with build scripts.

This is probably because cargo is aware of examples and so the right way to build them is to run e.g. cargo run --example hello-triangle from the top level of the repo. But that doesn’t get you the web part of things.

Except Linux has a severe driver problem with Vulkan and a lot of the Linux devices I've been checking out don't support Vulkan even now after it's been out seven years

do you mean embedded Linux? bc Vulkan is definitely widely supported on desktop

So for example I am very interested in the Pinebook (laptop) and Pinephone by Pine64, and last I checked (which was just a few weeks ago), neither of these support Vulkan out of the box. The way it was described to me it was less about "embedded" per se and more that both NVidia and ARM currently reserve Vulkan support to their closed-source drivers, so Linux distros (and hardware vendors like Pine64) that insist on a fully open source stack are kinda locked out from Vulkan. Apparently there are Mali GPU binary blobs I could get from ARM and install on the Pinebook/Pinephone, but I'm probably not going to buy a device without knowing 100% for a fact I'll be able to draw triangles.

(And I did double check, there's an open source third-party NVidia Vulkan driver called NVM but it only kicked off last October and it's not clear if it's out of the experimental phase yet.)

to be fair, even for opengl nvidia has atrocious performance without the binary drivers

(and i would consider all of pine64's hardware "embedded," though i guess that's a bit nebulous)

on standard desktop hardware that isn't nvidia vulkan support is fine, and realistically people running anything beyond basic desktop applications on Nvidia will be using the proprietary drivers anyway

fwiw: mesa intentionally did not bother supporting vulkan on the hardware you mentioned because they decided that the hardware wasn't powerful enough for any vulkan applications to run with usable performance

yeah vulkan on x86 desktop linux is fine and has been for years - for game/graphics dev purposes ARM desktop linux should be considered a hobby realm where nothing works yet.

also there are major windows games that shipped with vulkan: doom 2016 and doom eternal, no man's sky, and some UE4 games (which all run great on linux via proton, probably in part because of this). so the apple ecosystem is by far the most conspicuous gap in its support landscape.

The purpose of the pipeline and bind group layouts is to explicitly declare the bindings you use in your code for future use. I believe they are needed if you are doing any kind of advanced dynamic binding of buffers and other resources to the shader. As an example from some WebGPU code I wrote, for this in the shader:

@binding(0) @group(0) var<storage, read> raw_fft: array<f32>;
@binding(1) @group(0) var<storage, read_write> smoothened: array<f32>;

I'd define the pipeline like such:

const computePipelineDesciptor = {
    compute: {
        module: computeShader,
        entryPoint: 'cm_smoother'
    },
    layout: device.createPipelineLayout({
        bindGroupLayouts:[ device.createBindGroupLayout({
            entries: [
                {
                    binding: 0,
                    buffer: {
                        type: "read-only-storage",
                    },
                    visibility: GPUShaderStage.COMPUTE
                },
                {
                    binding: 1,
                    buffer: {
                        type: "storage",
                    },
                    visibility: GPUShaderStage.COMPUTE
                }
            ]
        })]
    })
} as GPUComputePipelineDescriptor;

At some point, you want to take control from the shader compiler to more explicitly allow you to declare what exactly you want to do with your data, and that's where layouts come in.

Excellent article btw, I greatly enjoyed reading it. If there is one more little thing I could add is a recommendation for PlayCanvas and Babylon.js in the "I just want to write my script tags" section. Babylon is an alternative to Three, while PlayCanvas is a fully fledged web-only game engine, both with WebGPU support

Thanks! I actually do link Babylon.js and PlayCanvas in footnote 17 but I acknowledge both that and the idea of a footnote in a blog post are a little obscure :)

EDIT: I updated the footnote to be a little more clear what it's linking.

Oh yeah, I noticed the footnotes only after writing my comment, in hindsight probably should have edited it. The clarification is a good addition though, thanks again for the article, I've shown it to my "Web-skeptical" friends and managed to at least slightly skew their opinions on the future of graphics programming, so that's something

If I were a cynical, paranoid conspiracy theorist, I would float the theory here that Apple at some point decided they wanted to leave open the capability to sue the other video card developers on the Khronos board, so they are aggressively refusing to let their code touch anything that has touched the Vulkan patent pool to insulate themselves from counter-suits. Or that is what I would say if I were a cynical, paranoid conspiracy theorist. Hypothetically.

iirc Apple has said in WebGPU meetings that they can't support SPIR-V because they can't implement Khronos specs due to confidential legal proceedings, so

Very cool post! As a webGL user maybe I need to get on this and start learning it. My only comment: you've used a picture of Steven from Steven Universe Future (2019), not his jacketless kid design from 2013.

Even with my stumbling not-in-software-dev understanding of the details this was a fascinating read. DirectX and OpenGL were just names to me that people would seem to ascribe to good/bad graphics performance at random and now I appreciate a bit more just how deeply fucked this field is for a layman to understand.

My perception is it was Direct3D, not OpenGL, which eventually managed to wrangle all of this into a standard, which really sucked if you were using a non-Microsoft OS at the time. It really seemed like DirectX (and the "X Box" standalone console it spawned) were an attempt to lock game companies into Microsoft OSes by getting them to wire Microsoft exclusivity into their code at the lowest level, and for a while it really worked.

There were four strategic goals of DirectX, with different priorities at different times:

  1. Make Windows gaming competitive with the consoles, so they had the same titles.
  2. Make Windows gaming better than Mac.
  3. Make DirectX better than OpenGL/Linux/SGI/etc
  4. Bring 3D to the Windows desktop.

#1 was basically just to make Windows a decent single(ish) platform for people doing cross-platform games (remember DirectX included things like input, audio, 2D, etc). And it worked. Lots of console games came to PC, everybody happy, no bad motives there.

Very quickly it became clear that #2 was going to be trivial because every even year, Apple would introduce a new and completely different gaming ecosystem, and every odd year they'd refuse to talk to game devs at all. In theory Metal fixed this, but... we'll see.

#3 was initially absolutely cut-throat, including the devious fuckery of Fahrenheit ( https://en.wikipedia.org/wiki/Fahrenheit_(graphics_API) ), but as OpenGL got cruftier and cruftier, while DirectX wasn't afraid to throw old junk away, it got easier and easier. The competition was really only reawakened by Vulkan.

#4 was because everybody was paranoid that 3D would be the Next Big Thing for the OS, and Microsoft was sure that they needed a 3DAPI they could embed deeeeeep into the OS so that things like Excel and Task Manager could use it (yeah...). So obviously it had to be their 3DAPI - they're not going to put someone else's API at the heart of their OS. Anyway, this absolutely succeeded - there's almost no native 2D API left, everything graphical the OS and apps do is layered upon DirectX (DX9 initially) and managing graphics memory seamlessly with multiple contexts was probably the most difficult part of that. Was it all worth the huge effort? Unclear - we basically just got a fancy Alt+Tab animation and the edges of windows have shadows, but fixing all that stuff certainly did make life much easier for gamedevs.

This is interesting context. Incidentally, since I was worried the history section was already too long, I didn't even get into Apple's ill-fated (and, frankly, completely irrelevant to anything that came after, although it had some really interesting features that OSes today still haven't replicated) QuickDraw 3D, but that is a Story.

There was a truly terrifying amount of toxic mud being thrown at the wall in those times (VRML!), because everybody was convinced that (a) 3D was going to be everywhere (2) everybody needed to share standards to share content and (3) they wanted to own that standard. The corporate backstabbing was epic!

really underselling the genie effect when you minimize windows on Mac. that one moved computers off the shelves lol, and it's almost the only graphical flourish from the first release of OS X that still persists in the Mac today

Great post! Excited that the WGPU future is finally here. It's seemed like a good default for Rust graphics for a while, but having it on the web makes it feel Real™️.

(The stuff re: Apple and their behavior in WGPU sounds right, as someone who was not involved in WGPU but was adjacent to the Rust folks who were stakeholders at the time)

By the way, have you noticed the cheesy Star Trek joke yet? The companies with seats on the Khronos board have a combined market capitalization of 6.1 trillion dollars. This is the sense of humor that 6.1 trillion dollars buys you

Nah, it's because the first version of Vulkan was literally just AMD's "Mantle" API with a search-and-replace on the prefix (I am 100% serious about this). Mantle was itself a pun - it's the layer around the "core" you see, so there was lots of imagery of volcanos and magma and things being hammered on subterranean forges (and of course it's red because AMD).

And when it needed a new name, because nobody could consistently spell "Hephaestus" they settled on his Roman name Vulcan instead, except to actually AVOID the Star Trek problem they used a K. And that's why the armadillo is waving a blacksmith's hammer around.

So yes of course it's a pun. But deliberately not a Star Trek one.

tried out the wgpu examples on desktop over the weekend, very keen to see this in a browser besides chrome, ie Firefox! i looked around for a "how is webgpu support coming along in FF" page like they've done for other major features/changes but couldn't find one, any idea if there's a decent place on the web to track that progress?

There's an experimental webgpu mode for firefox but last time I checked (a few weeks ago) my samples didn't work in it. I didn't get around to exploring why not. Actually I should probably do this now.

If you want to track things at a finer level than the links already posted above, I'd suggest the WebGPU Matrix channel or the "In WebGPU we Rust" Matrix channel (the channel for wgpu which is the Firefox component). dm me on mastodon or something if you need the addresses.

Update: I did a retest and it turns out neither of my WebGPU tests/samples work in Firefox Stable with the webgpu hidden flag enabled, but both of them work out of the box on Firefox Nightly. Not sure what to make of that but if you want to test drive WebGPU on Firefox maybe try Nightly for now.

Hm, that might be because you're on a Mac. But if the Windows version exposes the flag and yet the feature is broken then it comes to the same place in the end.

Unfortunately currently (112.0.2) it does do one thing: It causes the browser to start advertising the presence of WebGPU, even though WebGPU contexts cannot be requested from canvases. So in one of my code samples, the "no WebGPU! Better show an error message!" detection does not work *_*

I hadn't heard of dawn and then of course I had to click through to the CMakeLists.txt file. Yet another "Google write CMake that doesn't instantly make me want to crawl into a hole and pass away challenge (impossible)"

What a read! Good history lesson and introduction to WebGPU, got me interested in looking into it further. I've been down in the Vulkan mines for a good while, so I'm definitely intrigued by something that's a little more ergonomic than Vulkan, but which is also not OpenGL, and keeps the low level vibes.

WebGPU does look neat. I'm a bit worried it will quickly gain adoption as the interface people program against though, as my current computer is able to only do OpenGL 3.3 (Intel Integrated) / OpenGL 4.3 (NVidia), and I'd rather avoid having to replace it just because the new shiny user-facing API requires the new shiny low-level API my hardware doesn't implement.

wgpu actually also has something like this (in fact, they have a mode that runs on top of angle) but my experience if you ask the wgpu folks about it is they will try to talk you out of using it >_>

What a great longpost! History note: AMD's Mantle (already mentioned in the comments I see) was the real first effort in this direction, and AZDO I think was more of an attempt to get GL specifically closer to that level.

Production games like Battlefield 4 shipped with a Mantle renderer actually, and I saw a project that implements Mantle on top of Vulkan to preserve these renderers!

The NVidia AZDO GDC talk happened right in the middle of Valve's abortive Steam Machine push, and right after the Steam Dev Days event where Valve spent a lot of time trying to convince developers to port to Linux. Absolutely nobody was making OpenGL games except John Carmack, so I can't imagine why anybody would care about AZDO on OpenGL otherwise.

Yeah, I was trying to imply AZDO is the general idea underlying these APIs rather than suggesting the actual GDC talk/buzzword literally inspired all these APIs. Wasn't sure how to word that.

I did totally blank on mentioning Mantle in the timeline!

I don't do graphics programming often(despite meaning to get more into it, and this might be a good opportunity), but I really enjoyed this post, particularly the history section. Thanks for the read!

Will perpetually wish that WebGPU didn't come along with another NIH shader language. We're getting one more with SDL_gpu as well so soon there will be 200 different shader languages you have to care about

EDIT: Also just remembered that pipelines are on the way out in Vulkan* so it will be interesting to see if WebGPU later has to do the same or if they will stay stuck on pipelines

It seems like it's much, much easier to efficiently implement pipelines on top of shader objects than to try to do the opposite. I'd be very excited to see a shader-object-like mode in WebGPU, I assume(?) what determines whether the wg goes for it is how well the shader-object-like model efficiently maps to DX13 and Metal.

This said, how quickly should we expect support for the shader object extension to spread? It's in… Vulkan 1.3.246? Is that right? With OpenGL I'd usually expect many old video cards to never get driver support for new versions; are things different with Vulkan?

My understanding is that much more of the vulkan stack lives in user space and the OS, at least on Linux. You could probably also emulate shader objects on top of pipelines and I suspect many people would want to be able to write their application that way

I'm pretty interested in seeing how widespread shader objects become too. I've heard a couple of people express that they don't have much hope for them making inroads on mobile, but I really don't know what to expect.

They're not different with Vulkan on Android 😭

and probably not much different on other platforms either, maintaining support for several radically different kinds of hardware through one API is a lot of work, I'd be shocked if there's a vendor out there that doesn't just fork the driver at some point and put it into maintenance mode. but there will be some GPUs that are recent enough to get the new extensions, for sure.

As an observer this was a particular point of frustration for me. With webgpu supposed to be the friendlier version it's still tied to the complicated pipeline objects, and then vulkan goes and decides they weren't that useful in games and now everything is upside down. I hope that does spread downwards.

Nice to see more enthusiasm for webgpu, especially webgpu on native (the name is unfortunate, just like with wasm). Do note the firefox backed version isn't limited to Rust, I've been using it from the C FFI all along: https://github.com/gfx-rs/wgpu-native. For a while both implementations target the same C header, so the differences are very minimal like you explained in the post.

About targeting shaders from the language, since I use the Scopes programming language (which I surely mentioned to you on twitter sometime) this has been a feature from the beginning for me, and it's indeed nice over here! For a bit I was scared that it was going to be taken away but indeed on desktop they still accept SPIR-V bytecode and GLSL as well. Currently I'm writing wgsl anyway because our bytecode doesn't pass the naga validation (even though it worked fine before), but I'll try to fix that along the way.

Wow this is incredible. I kinda hate browsers, I mean there’s reasons all the way back to IE6, and Electron certainly hasn’t helped. So I had kind of ignored this whole space as being slightly cursed? But hearing that it’s abstracted enough from the browsers that it makes sense to use it native, with a lot less overhead than you’d expect, has made this sound much more interesting. We really do just want some cross platform stuff and honestly, nothing brings all the major players to the table like the web.

I also had never heard before that Vulkan was never meant to be written by humans, I’d heard it was fiendishly complex and then was surprised that people were excited by D3D12 and Metal.

Also I’m amazed that you made Apple’s decision to deprecate OpenGL sound reasonable hahaha, kinda goes to show that Apple doesn’t get the marketing team to talk about engineering decisions, you get the consumer facing half of the company being all friendly and the technical half is seemingly code-of-silenced into being unable to explain even the simplest things satisfactorily.

I suspect you may even be underestimating the impact of WebGPU. I'll make two observations.

First, for AI and machine learning type workloads, the infrastructure situation is a big mess right now unless you buy into the Nvidia / CUDA ecosystem. If you're a research, you pretty much have to, but increasingly people will just want to run models that have already been trained. Fairly soon, WebGPU will be an alternative that more or less Just Works, although I do expect things to be rough. There's also a performance gap, but I can see it closing.

Second, for compute shaders in general (potentially accelerating a large variety of tasks), the barrier to entry falls dramatically. That's especially true on web deployments, where running your own compute shader costs somewhere around 100 lines of code. But it becomes practical on native too, especially Rust where you can pull in a wgpu dependency.

As for text being one of the missing pieces, I'm hoping Vello and supporting infrastructure will become one of the things people routinely reach for. That'll get you not just text but nice 2D vector graphics with fills, strokes, gradients, blend modes, and so on. It's not production-ready yet, but I'm excited about the roadmap.

It's been extremely frustrating for me to see the compute world (especially stochastic parrot development but other things like photo editing and offline rendering too) not move to Vulkan and insist that proprietary CUDA and painful OpenCL are the only two options. I've heard tons, tons of excuses about Vulkan compute not quite having the features that OpenCL has, not working in that or this way and whatever else… and now suddenly when a limited and simplified subset-ish sort-of-but-not (handwaves) of Vulkan shows up in browsers, suddenly there is interest? Suddenly turns out the excuses were mostly just excuses and it's all possible to do.

It's been extremely frustrating for me to see the compute world (especially stochastic parrot development but other things like photo editing and offline rendering too) not move to Vulkan

Just to give you hope, pretty much all of my GPGPU work is using Vulkan compute features. I even write Adobe After Effects plugins that exclusively utilize Vulkan/MoltenVK compute and do all my graphics research in it.

Thank you very much! This is a fascinating, informative and fun read that I will be bookmarking, rereading, and using as a reference. And definitely not too long! I love history, and I love footnotes, so a very pleasant way to spend an evening. Again, thank you.

Few years ago I had an "Intro to Computer Graphics" class at uni. We did a lot of WebGL (because not having to install anything means is the easiest platform to develop to).

For the final project I ended up doing a FLAM3 renderer on WebGPU. It was really crappy (just atomic incremenrs on a big buffer) but it worked incredibly well!

Initialization headaches aside WebGPU and WGSL are really nice to use. Even when comparing them to WebGL. It's kind of incredible that the low~ish level API feels better than the high~ish level one.

Also GPUs are amazed me with how fast they are. Even for crappy code.

If I may add one more piece of info, regarding SPIR-V and WGSL specifically: Google has a bi-directional SPIR-V<->WGSL compiler called Tint ( https://dawn.googlesource.com/tint ), which is used in Chrome's WebGPU vulkan backend and seems to be quite feature complete. Not sure of any work specifically on that front, but with enough "convincing" you could likely run it using WebAssembly, and thus ship SPIR-V with your WebGPU applications. (Or write your shaders in HLSL/GLSL, compile them to SPIR-V and then back to WGSL for web deployment)

Yeah, I believe "embed tint" (or in Rust world it's Naga) is in fact the recommendation if you want to dynamically construct SPIR-V. The main thing that worries me about this is that if I am targeting web I want my executables to be as small as possible. I haven't had the chance to do size tests (my first successful WebGPU-via-WASM build was like, this past Friday) but I'd be concerned about how much larger embedding a SPIV-to-WGSL compiler would make my .wasm. (And there might be certain awkward sizes, like say the neighborhood of 200k, which would be small enough I'd be happy to include it if I had a need to dynamically generate shaders, but large enough it might inspire me to just rearchitect my software so it never dynamically generates shaders.)

That was a really good read! I havent been involved much in any shader shenanigans just yet (web or not) so I am somewhat intimidated by the Hello World sizes but this does kind of tempt me to delve into shaders

Disclosure: Some edits I have made to this post since posting: Clarified the timeline of Metal vs Vulkan; clarified the bit about AZDO to prevent implying the AZDO talk itself inspired all those graphics APIs, which would be pretty implausible considering the timeline of Metal and Mantle; fixed the diagram images for visual glitches and to make it clear render passes are created on views, not textures; wrote out names for the threejs alternatives in the footnote; typos.

Also the "sample Rust repo" linked was initially missing a commit which was present in the "compiled form running in a browser" linked in the same paragraph. The license was missing. But that's pushed now.

2023-05-12: Corrected an error in footnote 6.

I've been using WebGPU as a library in standalone / not-in-the-browser Rust and it's pretty great. Just a thin, flexible layer with Vulkan-like semantics that abstracts away all the nasty stuff you'd have to abstract away anyway, so you don't have to reinvent the wheel.

It's a lot to take in and I feel like WGPU / Vulkan-like things make the most sense to learn after you get done being frustrated with OpenGL in the same way that Rust makes the most sense to learn after you get done being frustrated with C++, but on the other hand it's also well-documented and you can definitely do it if you set your mind to it!

I could help out a bit. I'm not the best teacher - I used to be better at teaching but then customer service jobs scrongled my conversational style - but I'd be willing to try.

Learn from a yinglet? SIGN ME UP

Haha, yeah, like I'm super interested in getting a GPU to do different things, like it can definitely draw a triangle, but what else can you do with that? What other kinds of data can you process or represent within a GPU's domain, like... not exactly CUDA/OpenCL stuff, but just abstracting the whole idea of what data is, if that makes any sense? I don't know if I am, and I probably sound like a total noob loon.

I haven't seen this mentioned, so I'll just add that the lowest friction method I've found for playing with this stuff on Android is to load Chrome Canary and enable

chrome://flags/#enable-unsafe-webgpu

edit primus: it's probable you can also just do this on chrome, but i usually don't use chrome for anything and compile my own chromium apk to be able to run apps that require chrome.

however, the build is a bit of a bother, so i haven't done it yet.

edit secundus: firefox nightly for android now seems to have webgpu enabled by default