Good day,
Before we get started, I have an important update for GM Shaders. I’ve been writing mini tutorials since August 2022 and over that time period I’ve released 47 mini tutorials. I really enjoy writing them, however I’ve come to the conclusion that I need to rework my process to be more sustainable long term. As of now, I average about one day per week working on these tutorials. Often more, but I also skip some weeks.
My primary income comes from private programming contracts, which also the reason why I’ve been absent these last two weeks.
Full disclosure, I currently make $655 a year from my paid subscribers, which I appreciate greatly.
For 2024, I’d like to take these tutorials a little more seriously and to do that, I’ll need to bring my income up a bit, so I can afford to dedicate more time to these tutorials. It’s extremely important to me, to keep creating free tutorials for public usage, however if I don’t make a change I’ll likely have to cut back on the amount of tutorials I’m creating, so I think I’ve come up with a fair compromise: Every other tutorial will be freely available and for the in between tutorials, I’ll release the draft skeleton version, but the fleshed out details will be paid only. I believe this is the best compromise. Paid subscribers get something valuable for their contributions, but free users will still get full tutorials and draft tutorials. This doesn’t require me to commit even more time than I have and will hopefully allow me to shift more focus towards these tutorials.
Here’s a sneak peek at some future tutorials: Gamma Correction, Generalized Blur, Volume Shadows, One Pass Shaders, “Xor Lang”.
If this sounds good to you, here’s a special offer to jump in.
So this week’s tutorial is free, next week will be the draft version. Please let me know your thoughts in the comments. Thanks!
Now let’s get to today’s topic.
WebGPU
We have two questions to answer today: What is “WebGPU” and why should we care?
Starting with the latter, if you’re a GameMaker user, you’ll want to know because it’s coming to GM with the new runtime update.
Other game engines have been implementing it recently too, and it looks like it’s going to be everywhere soon, so it’s a good idea for everyone in graphics to learn about it.
WebGPU is designed to replace WebGL as the new standard for graphics. Like WebGL, it’s designed to be cross-platform, but also more performant, supports more features (like compute shaders!), deeper low-level access and is more secure.
This is particularly exciting to me because it finally brings some more modern features to cross-platform and compatible context. It will give us deep, low-level control that we’ve never had in GM or on the web!
Quick Overview
If you’ve ever tried to make your own game engine, you’ll know how much of a pain it is to work on anything cross-platform.
Cross-platform
You may want to use Vulkan for its performance, but Apple is stubborn, so if you want to support macOS or iOS, you’d also need to implement Metal. OpenGL ES is nice for cross-platform support, but does not give you the same level of control. One of the great appeals of WebGPU is that it helps bring all of this together for a feature-rich, low-level, cross-platform API.
It’s important to note, that WebGPU is quite new and is still in experimental stage for some browsers.
Low-Level Access
WebGPU offers finer control over the allocation, usage, and management of GPU resources. WebGPU also makes fewer assumptions about the pipeline states (shader stages, rasterization, blending, depth/stencil and more). You can completely omit parts that you don’t need, which allows for greater performance at the cost of being a little more hands-on. Uniform and storage buffers allow for better data sharing between the CPU and GPU, especially with larger sets of data. Finally, it’s more modern, so it’s better with supporting modern GPU features.
This is huge for us GameMaker people, since we’ve been stuck on GLSL 1.00 for a long time now.
Compute Shaders
The GPU is great with processing things in parallel like vertices or fragments/pixels, but modern GPUs can do much more than that. You have heard of tessellation, geometry shaders or compute shaders. Compute shaders are powerful because they are a more generalized type of shader, designed to handle other types of data (not just vertices or fragments). They aren’t tied to a particular shader stage or even rendering at all. This is why compute shaders are often used for particle/fluid simulations or even machine learning, where pixel and vertex shaders are less appropriate.
It also opens doors that are not possible in fragment shaders, like full read and write control (you could write to two pixels at the same time, for example). It can be quite difficult to wrap your head around, so I recommend playing around with compute shaders to learn more about them. More on that later.
WGSL
WebGPU comes with its own shading language called “WebGPU Shading Language” or WGSL for short. WGSL is much stricter than GLSL and has a syntax closer to Rust or Swift. I’ve been assured that GameMaker will still support GLSL, which will be compiled to WGSL as needed and I expect many other engines will do the same, but I’ll quickly go over the differences in case you want/need to know more.
GLSL shaders are typically compiled at runtime by your graphics driver, which can lead to differences across hardware and drivers. WGSL is designed for shaders to be validated and potentially compiled ahead of time, which leads to much more consistent results across various hardware.
Note: WGSL is still relatively new and may be subject to further changes.
Let’s do a quick overview:
Data Types
You have 4 main data types: floats, ints, unsigned (so no negative values) ints, and bools. These can be initialized in two ways, using “var” or “let” (for constant or unchanging values). Here’s what variable declaration looks like:
var a: f32; //32-bit float
var b = 50f; //Initializes as a 32-bit float
var c = 0i; //Initialized as a 32-bit signed int
let d = 8u; //32-bit, unchanging, unsigned int.
As of writing, only 32-bits are supported natively. There are also abstract floats and ints for high-precision 64-bit constants.
Vectors are similar to GLSL, but the data type (no bools) must be specified:
var e: vec2<f32>; //Vec2, 32-bit float
var f = vec3f(50); //Vec3, 32-bit float
var g = vec4u(8); //Vec4, 32-bit unsigned int
vec3f
is an alias for “vec3<f32>
”, which I personally prefer.
One neat feature of WGSL is that if you already know a variable is a float, you can do things like x/4, and it won’t interpret 4 as an int.
Functions Definitions
Custom functions start with the keyword “fn” and arguments are defined with their data type like how they are initialized. The return data type is indicated by the “->”.
fn functionName(a: f32, b: vec2f) -> vec2f
{
return a + b;
}
The main functions are defined similarly:
@vertex
fn main() -> @builtin(position) vec4<f32> {
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
}
At “@vertex” indicates the entry point of the vertex. We also do similar for fragment, and compute shaders. “@builtin(position)” is like the equivalent of gl_Position, the output clip space vertex position.
Variable Qualifiers
In old school GLSL, we have “attributes” and “varying”, which were later replaced with layout/location stuff. WGSL is similar, with input and out variables set by “@location(x)” instead of name. Uniforms are grouped and also each need a unique binding number. Here’s an example.
@location(0) var<in> vertexPosition: vec3f;
@location(0) var<out> fragmentColor: vec4f;
@group(0) @binding(0) var<uniform> mouse: vec2f;
That’s just a quick overview of the major differences. There are many more nuanced syntax differences that I can’t easily summarize. For a comprehensive overview of the language, I recommend taking the Tour of WGSL, but I find the best way to learn is to look at code examples and experimenting.
Here’s a fragment shader I wrote, converted to a “WGSL” compute shader:
There are a lot of other great examples on that website. It’s basically ShaderToy for WGSL/Compute shaders.
Conclusion
WebGPU brings a lot of exciting possibilities to the table. Especially so for web devs and GameMaker users. Today we looked at cross-platform support, low level optimizations, new features and my favorite: compute shaders.
Compute shaders bring the power of the GPU to many other data-crunching contexts, that just we’re practical before. Compute shaders are often used for particle/fluid/physics simulations, terrain generation, image filters, machine learning and many other contexts. It can be a bit confusing going from frag shaders to compute shaders (it feels a bit like going from sequential CPU operations to parallel GPU operations), but if you take the time and experiment with it, you’ll find many places where it can optimize your games and open new possibilities.
For more in-depth reading on how WebGPU is implemented, here’s a good place to start.
WGSL is a rather picky language compared to GLSL, but it results in more consistent and optimized results. It’s quite verbose, requiring certain formats and conventions, but it’s good to have as an option. GLSL can be rather on predictable on various hardware/devices so hopefully WGSL will help mitigate that.
I’m going to wrap it up here. If you’d like to subscribe, you can do so here. Paid subscribers have access to all full tutorials, while free members get every other one.
Extras
The last tutorial was all about mipmapping, and after writing it, I found an interesting technique developed by Jacco Bikker to optimize mipmap texture sampling:
jacco.ompf2.com/2020/04/10/optimizing-trilinear-interpolation
Unfortunately, GM’s GLSL 1.00 doesn’t support texelFetch, but perhaps we’ll be able to implement this when the new runtime comes along.
”Height Map from Normal Map” by “@Mirko_Salm”. Usually people convert heightmaps to normal maps, but here’s an interesting technique for doing the reverse.
That’s it for this week. Thanks for reading and have a good week.
-Xor
You definitely should be getting paid more than $12.60 per tutorial (1 day per week, $655 per year). What is that if you work it out hourly?
Your work is very niche but the information you’re giving is absolutely priceless to that niche. Your technical knowledge and expertise is incredibly rare.
Most of the stuff you write about goes way over my head but I flick through in case I understand something. But I’m mainly here because I’ve seen your name pop up a lot on forums and I know you know your stuff.
Is it an exposure issue maybe? Not enough people know about your work? The only reason I found this newsletter is because I was signing up to someone else’s and it recommended yours and I was like “oh I’ve heard of that guy”.
From your graph, it looks like you’ve had steady growth over the last year (it’s doubled?). That’s good, are you marketing it?
In my experience, putting things behind a paywall without substantial enough exposure / free content to get people in will stifle growth.
So imo, your idea of providing a skeleton newsletter for free and charging for the details is a good idea. But ultimately it’s rare that I even open newsletters (it tends to be when I’m bored).
My favourite kind of marketing is to make something pretty, but that’s tough to do when something is very technical.
I obviously can’t speak for everyone, but if I were to become a paid subscriber it would be on principle. I think the work you do is great and the fact you publish it for free is very honourable. If there had been a smaller donation option but for no benefit, I’d be much more likely to donate (eg one off, or just less per year).
When I first learned how to code as a kid, I had a budget of $0 so anything behind a paywall was simply off limits for me. So I truly respect any creator that finds a way to monetise something useful without charging the customer directly.
But you have to eat too. I don’t really know what to suggest. But I thought I’d post this in case you found it insightful!