Writing Compute Shaders In/With Zig

Hi!

I’m currently trying to figure out the best way to use GPU acceleration within Zig programs. These compute shaders would need to be written in a language that can cross compile between GPU architectures, especially since I’m on MacOS so CUDA isn’t really an option. The compute shaders I intend to write are not very advanced (ie i’d be making things like parallelized Monte Carlo simulations), and I am more than willing to give up a bit of speed if it means not having to define every last detail perfectly.

From what I’ve been able to gather my options are:

  1. Use a library/bindings to write shaders in OpenCL/WebGPU
  2. Use Zig’s SPIR-V backend (it wasn’t super clear if that backend is mature enough to actually be used)

If I went the library route, it would be nice if doing so did not require jumping through a million hoops. Of course, I don’t expect a solution as ergonomic as those available in game engines like Unity but when I tried zgpu, I was unable to get even the most basic programs to work (definitely a skill issue on my part but having to declare each buffer multiple times in multiple ways for each shader is bit of a pain to say the least). This also means I want to avoid learning how to write raw Vulkan.

Thank you for your help!

wgpu-native could be useful for you.

If you just want to render some stuff with compute-generated data you could look into the sokol-zig bindings for the sokol headers, this comes with a complete (offline) shader-cross-compiler.

The Zig bindings currently only have one compute example which computes 3D particles in a vertex shader:

…basically this WebGPU sample, just in Zig (needs a WebGPU capable browser: instancing-compute

The C samples have a couple more compute examples:

The shaders (sokol-zig/examples/shaders/instancing-compute.glsl at master · floooh/sokol-zig · GitHub) are written in GLSL, compiled via sokol-shdc (sokol-tools/docs/sokol-shdc.md at master · floooh/sokol-tools · GitHub) and embedded as blobs in a code-generated Zig module (sokol-zig/examples/shaders/instancing-compute.glsl.zig at master · floooh/sokol-zig · GitHub) all integrated into the Zig build process (e.g. edit the GLSL shader code and the build system will take care about rebuilding the shader’s output Zig module).

Only downside so far is that there’s no way to get data from the GPU side back to the CPU side. E.g. the only thing you can do with the compute shader output is to use it as input to other shaders.

PS: sokol-zig bindings repo is here: GitHub - floooh/sokol-zig: Zig bindings for the sokol headers (https://github.com/floooh/sokol) · GitHub

The original C header repo is here: GitHub - floooh/sokol: minimal cross-platform standalone C headers · GitHub

5 Likes

You wouldn’t use neither library nor bindings to write shaders. You pick a language and compile your shader to your target. Which API you’re going to use to load your shaders is a completely different matter.

My understanding is that it is currently unusable. There’s no documentation for it anywhere.

I’m not sure I understand what you meant here. Are you talking about having to declare the buffer layout in shader and then again in CPU code? Because if that’s the problem, there are options to solve this, like the Slang language and the reflection tool for GLSL.
Also, you can declare a single buffer layout that is shared by all your shaders. Each shader will use some part of it.

Sadly, GPUs are powerful, but tapping into that power requires talking their language. Vulkan is verbose, sure, but every step makes sense and is tied to some real GPU concept, that you can transfer to any library. Any abstraction is necessarily taking this power away and funneling you into whatever their vision is. If your vision doesn’t match theirs, you’ll be stuck.

I think your best bet is to learn vulkan :person_shrugging:
Your choice of shading language is less important, but it seems like people are gravitating towards Slang.

I am going to be the contrarian and say to just not bother with cross-platform GPU compute; it is just a constant uphill battle. Pick a reasonable target matrix and develop for that! Metal + CUDA gets you very good coverage already.

It sounds like you wont need that many raw lines of code if you are doing Monte Carlo simulations. Metal is surprisingly not that difficult to pick up if you know CUDA, and you would get the benefit of not having to learn stuff like MoltenVK and all the weirdness that comes with that :slight_smile:

Also, getting actual native performance is a big plus compared to the speculative benefits of not having duplicated code.

2 Likes

It can be used, but it’s not really user friendly yet. Example of simple shaders written in zig:
https://github.com/Snektron/vulkan-zig/blob/master/examples/shaders/vertex.zig
https://github.com/Snektron/vulkan-zig/blob/master/examples/shaders/fragment.zig

5 Likes

Thank you all for your help! This is all super helpful! It’s looking like slang is an ideal solution for me, especially since my shaders would then be super easy to bring over into python which is a more familiar interface to my colleagues.

Does anyone have experience working with Slang in Zig? I’m very new to GPU programming (if that isn’t obvious) and I’m really struggling to understand how to “link” the Slang compilation and execution to my Zig code. To my understanding, I would need to:

  • Add the slang compiler into the build system (this is definitely the easiest to figure out)
  • Establish the bindings with Slang’s reflection
  • Dispatch the shaders from Zig code in whichever platform-dependant format (ie Slang on macos would compile down to Metal vs Vulkan or Cuda on everything else)

I’m a little lost and am not expecting a full crash course in GPU programming in the replies of forum thread, but links to good resources would be very appreciated!

Update:

After some more searching, I found raugl/slang-zig and orderrrr/slang-zig (the former seems more promissing). Might these be good options?

1 Like

Wasn’t aware of this, super cool. Are there any known limitations when targeting spir-v? Would like to swap some of my shaders out with this to get rid of a glslc dependency that I currently have.

According to their Spir-V Specifics Documentation Page :

  • It can only target Spir-V 1.3 and later. v1.0, v1.1 and v1.2 are still experimental
  • Unsupported GLSL keywords
    • owp : RelaxedPrecision, on storage variable and operation
    • mediump : RelaxedPrecision, on storage variable and operation
    • highp : 32-bit, same as int or float

And that’s seemingly it. The documentation is honestly pretty concise, all things considered, and goes into detail about everything supported (which seems to be almost everything)

1 Like

Yes, that is correct.
Adding the slang compiler in the build process adds a system dependency, which is always a bummer, but it is what it is.
For the bindings part, you’ll probably need to parse the bindings format output by the compiler and translate it into Zig. It shouldn’t be that hard.

Hi, I’m by no means an expert in gpu programming but last year I started to learn a bit about it and from a different topic here I first learned about slang and thought I might try that one for the cross compilation capabilities.

Here is my messy hello world test repo for getting a basic compute shader to work with zig and slang: Joel Koch / zig_gpu · GitLab

Note that you need zig version 0.16.0-dev.1484+d0ba6642b for it to work, it doesn’t compile with 0.16.0 it seems.

I tested the vulkan version on my machine and it works, I can’t remember if the metal version ever worked, sorry!
The vulkan version shows errors, so I think there is clean up code missing :smile:

Regarding resources:
The slang docs were quite useful, I remember that they also have a page about a pattern they prefer for organizaton which was quite interesting to me as I didn’t know anything at all about GPU programming. But I think you don’t need that if your shaders are few and small.

Then, there is the vulkan tutorial about compute shaders but you might have to follow the whole tutorial from the beginning to understand the compute shader section.
Here is a related github repo: VulkanTutorial/code/31_compute_shader.cpp at main · Overv/VulkanTutorial · GitHub

Someone recommended the Compute shader 101 video, that was interesting to watch.

For more advanced topics I found the modular docs and puzzles interesting, though that won’t directly help you to write shaders for zig.

I hope it helps and have fun!

4 Likes

Thank you this is so helpful! Will definitely look through all those resources! Those modular puzzles look like a great way to learn.

If anyone is looking for an actual implementation and wiring of SPIR-V shaders written in Zig, I recently ported my vertex shaders from glsl to Zig and am now compiling them with the Zig build system (without an external dependency). You can see my changes on this PR, I think the most interesting shader to look at is the Zig implementation of Eric Lengyel’s slug font rendering algorithm, you can find the source here https://codeberg.org/shahwali/knots/src/branch/main/src/gpu/backend/vulkan/shaders/slug_vertex.zig

Some limitations I ran in to (re my comment above, asking if anyone was aware of any limitations).

  • I was unable to port my fragment shaders due to their reliance on some glsl builtins like fwidth and texelFetch. I tried implementing them via inline SPIR-V assembly but ran in to the following SPIR-V codegen bug: https://codeberg.org/ziglang/zig/issues/35238
  • The gpu namespace is very minimal currently, and I had to implement some util stuff to reduce boilerplate for input/output bindings.

Besides that, I was quite surprised at how easy it was. I was expecting a lot more friction. And having my shaders in Zig is gonna be really nice as it gets rid of my glslc dependency, also nice to write my shaders in a much more powerful language!

6 Likes

Should improve the SPIR-V situation significantly:
https://codeberg.org/ziglang/zig/issues/35240

As well as vec2/3/4 and matrix types.