Skip to content

Instantly share code, notes, and snippets.

@alichraghi
Last active May 14, 2025 15:38
Show Gist options
  • Save alichraghi/cc4b1db0a0a556de4f85cf06f0e7a400 to your computer and use it in GitHub Desktop.
Save alichraghi/cc4b1db0a0a556de4f85cf06f0e7a400 to your computer and use it in GitHub Desktop.
Zig Shaders

What does it look like?

Here is a simple fragment shader with uniform buffers:

const std = @import("std");
const gpu = std.gpu;

const UBO = extern struct {
    object_color: @Vector(4, f32),
    light_color: @Vector(4, f32),
};

extern const ubo: UBO addrspace(.uniform);
extern var frag_color: Vec4 addrspace(.output);

export fn fragmentMain() callconv(.spirv_fragment) void {
    // Annotation
    gpu.fragmentOrigin(fragmentMain, .upper_left);
    gpu.binding(&ubo, 0, 0);
    gpu.location(&frag_color, 0);

    frag_color = ubo.object_color * ubo.light_color;
}

Ugly? Well, consider how much the language grammer would've to change for just moving stuff next to variable declaration or put-your-bikeshedding-here. Also SPIR-V is a large and growing format and adding keywords for all those decorations and types is an insane idea. At some point @builtins were considered for some types but flexible user-space solutions like std.gpu are usually prefered.

How to build?

In CLI:

zig build-obj shader.zig -target spirv64-vulkan -ofmt=spirv -mcpu vulkan_v1_2+int64 -fno-llvm

In build.zig:

const vulkan12_target = b.resolveTargetQuery(.{
    .cpu_arch = .spirv64,
    .cpu_model = .{ .explicit = &std.Target.spirv.cpu.vulkan_v1_2 },
    .cpu_features_add = std.Target.spirv.featureSet(&.{.int64}),
    .os_tag = .vulkan,
    .ofmt = .spirv,
});
const shader = b.addObject(.{
    .name = "shader",
    .root_source_file = b.path("shader.zig"),
    .target = vulkan12_target,
    .optimize = .ReleaseFast,
    .use_llvm = false,
    .use_lld = false,
});
// Use the emited SPIR-V with `shader.getEmitedBin()`

Note

int64 Feature is currently neseccary due to its use in builtin module. See ziglang/zig#21827

GLSL/HLSL -> Zig mapping

This is by no means complete, but it's a good starting point when you're looking to port some shaders between GLSL/HLSL to Zig.

GLSL HLSL Zig
gl_Position SV_Position gpu.position()
gl_VertexIndex SV_VertexID gpu.vertexIndex()
gl_InstanceIndex SV_InstanceID gpu.instanceIndex()
gl_FragCoord SV_Position gpu.fragmentCoord()
gl_FragDepth SV_Depth gpu.fragmentDepth()
layout(location=N) SV_Target gpu.location()
layout(binding=N) register() gpu.binding()
gl_GlobalInvocationID SV_DispatchThreadID gpu.globalInvocationId()
gl_LocalInvocationID SV_GroupThreadID gpu.localInvocationId()

How does inline assembly look like?

You can directly write SPIR-V assembly using the inline assembly feature. As it seems you must have a basic knowledge in both Zig's inline assembly syntax and SPIR-V so make sure to read Zig's inline assembly, SPIR-V Assembly Syntax and SPIR-V Specification docs.

Here's how std.gpu.binding() is implemented:

pub fn binding(comptime ptr: anytype, comptime set: u32, comptime bind: u32) void {
    asm volatile (
        \\OpDecorate %ptr DescriptorSet $set
        \\OpDecorate %ptr Binding $bind
        :
        : [ptr] "" (ptr),
          [set] "c" (set),
          [bind] "c" (bind),
    );
}

OpDecorate is an instruction with no result-id which means it has no output. normal input constraints are declared by an empty string and a % sign in the code. there's also constant constraints ("c") which takes a comptime known value and are determined with a $ sign. for more examples checkout std.gpu.

How can i help?

Write code. SPIR-V backend is in early stages so we are eager to see how it works for real-world examples so a reproducible bug in issue-tracker is appreciated. Btw, for a contributer friendly task, consider extending std.gpu :) If you have any further questions feel free to reach me (#alichraghi) or Snektron (#snektron) in Zigcord's #spirv-backend channel or ZSF's zulip.

@ktkk
Copy link

ktkk commented Feb 18, 2025

Is this available on the master branch? Currently std.Target.spirv.cpu doesn't seem to have a vulkan_v1_2 field.

@alichraghi
Copy link
Author

alichraghi commented Feb 18, 2025

yes sorry not yet. ziglang/zig#22889

@igaryhe
Copy link

igaryhe commented Mar 2, 2025

is it possible to bind storage buffers and textures at the moment?

@alichraghi
Copy link
Author

for storage buffers see ui.zig. as for textures theoretically, it should be possible with inline asm

@igaryhe
Copy link

igaryhe commented Mar 12, 2025

i'm trying to use build.zig to build the shader. i add an InstallArtifactStep by calling b.addInstallArtifact(shader, .{ .dest_dir = .{ .override = .prefix } });, but when running zig build it always export an compilation error:

zig build shader
shader
└─ install vert
   └─ zig build-obj vert ReleaseFast spirv64-vulkan 1 errors
error: failed to write: NotOpenForWriting
error: the following command failed with 1 compilation errors:
D:\scoop\apps\zig-dev\0.15.0-dev.34\zig.exe build-obj -fno-llvm -fno-lld -ofmt=spirv -OReleaseFast -target spirv64-vulkan -mcpu vulkan_v1_2+int64 -Mroot=D:\workspace\zig\nad\src\shaders\sample_frag.zig --cache-dir D:\workspace\zig\nad\.zig-cache --global-cache-dir C:\Users\dan\AppData\Local\zig --name vert --zig-lib-dir D:\scoop\apps\zig-dev\0.15.0-dev.34\lib\ --listen=-
Build Summary: 0/3 steps succeeded; 1 failed
shader transitive failure
└─ install vert transitive failure
   └─ zig build-obj vert ReleaseFast spirv64-vulkan 1 errors
error: the following build command failed with exit code 1:
D:\workspace\zig\nad\.zig-cache\o\88a4a4b3f93cc772a5032f5a6f27b816\build.exe D:\scoop\apps\zig-dev\0.15.0-dev.34\zig.exe D:\scoop\apps\zig-dev\0.15.0-dev.34\lib D:\workspace\zig\nad D:\workspace\zig\nad\.zig-cache C:\Users\dan\AppData\Local\zig --seed 0x1ff6c651 -Zc549fb455ba986cc shader

however if i directly runs the output zig build-obj command, zig could compile flawlessly. is there anything wrong with my setup?

@alichraghi
Copy link
Author

does this only happen when outputting spir-v? seems like a build system issue

@igaryhe
Copy link

igaryhe commented Mar 12, 2025

yes, i think it only happens when outputting spir-v. can you reproduce it on your side? my zig version is 0.15.0-dev.34+8e0a4ca4b

@igaryhe
Copy link

igaryhe commented Mar 17, 2025

i'm not exactly sure why, but the error log seems coming from this line: https://github.com/ziglang/zig/blob/2a4e06bcb30f71e83b14026bcbade6aac3aece84/src/link/SpirV.zig#L266-L267, and in a debug build of the compiler, i observed that this self.base.file is null:

D:\workspace\zig\zig\src\link\SpirV.zig:266:19: 0x7ff7d97b7755 in flushModule (zig.exe.obj)
    self.base.file.?.writeAll(std.mem.sliceAsBytes(linked_module)) catch |err|
                  ^
D:\workspace\zig\zig\src\link\SpirV.zig:197:28: 0x7ff7d95e98e2 in flush (zig.exe.obj)
    return self.flushModule(arena, tid, prog_node);
                           ^
D:\workspace\zig\zig\src\link.zig:847:77: 0x7ff7d94785eb in flush (zig.exe.obj)
                return @as(*tag.Type(), @fieldParentPtr("base", base)).flush(arena, tid, prog_node);
                                                                            ^
D:\workspace\zig\zig\src\Compilation.zig:2521:17: 0x7ff7d9477e5e in flush (zig.exe.obj)
        lf.flush(arena, tid, prog_node) catch |err| switch (err) {
                ^
D:\workspace\zig\zig\src\Compilation.zig:2458:22: 0x7ff7d947cebd in update (zig.exe.obj)
            try flush(comp, arena, .{
                     ^
D:\workspace\zig\zig\src\main.zig:4221:32: 0x7ff7d94f99ab in serve (zig.exe.obj)
                try comp.update(main_progress_node);
                               ^
D:\workspace\zig\zig\src\main.zig:3661:22: 0x7ff7d951fe9b in buildOutputType (zig.exe.obj)
            try serve(
                     ^
D:\workspace\zig\zig\src\main.zig:277:31: 0x7ff7d93f79ad in mainArgs (zig.exe.obj)
        return buildOutputType(gpa, arena, args, .{ .build = .Obj });
                              ^
D:\workspace\zig\zig\src\main.zig:212:20: 0x7ff7d93f6326 in main (zig.exe.obj)
    return mainArgs(gpa, arena, args);
                   ^
D:\scoop\apps\zig-dev\0.15.0-dev.56\lib\std\start.zig:631:28: 0x7ff7d93f6149 in main (zig.exe.obj)
    return callMainWithArgs(@as(usize, @intCast(c_argc)), @as([*][*:0]u8, @ptrCast(c_argv)), envp);
                           ^
D:\scoop\apps\zig-dev\0.15.0-dev.56\lib\libc\mingw\crt\crtexe.c:266:0: 0x7ff7dbf436db in __tmainCRTStartup (crt2.obj)
    mainret = _tmain (argc, argv, envp);

D:\scoop\apps\zig-dev\0.15.0-dev.56\lib\libc\mingw\crt\crtexe.c:186:0: 0x7ff7dbf4373b in mainCRTStartup (crt2.obj)
  ret = __tmainCRTStartup ();

???:?:?: 0x7ff85f99259c in ??? (KERNEL32.DLL)
???:?:?: 0x7ff860c2af37 in ??? (ntdll.dll)

however, this only happens on windows, and when i run this on a linux machine it works flawlessly.

@alichraghi
Copy link
Author

this only happens on windows, and when i run this on a linux machine it works flawlessly.

yeah that explains why i couldn't either lol. btw feel free to continue discussion in zulip. other compiler folks may have a clue

@VictorSohier
Copy link

I'm just wondering if you have at least the shaders for a full "Hello, Triangle" example in zig.

Also, I'm having some issues running the SPIRV output in vulkan, something about an unimplemented instruction. This very well might just be the fact that I'm running a 5700XT, but I doubt it since vulkaninfo tells me that my system can do vulkan version 1.4 and this is vulkan version 1.2.

@alichraghi
Copy link
Author

alichraghi commented Apr 24, 2025

I will try to get a working vulkan triangle example soon. perhaps just contribute it as an option to vulkan-zig.

something about an unimplemented instruction

Can you send the full validator message?

EDIT: See Snektron/vulkan-zig#181

@VictorSohier
Copy link

VictorSohier commented Apr 24, 2025

Sure, here's the full error:

ACO ERROR:
    In file ../src/amd/compiler/aco_instruction_selection.cpp:9022
    Unimplemented intrinsic instr: @store_deref (%1, %6) (wrmask=xyz, access=none)

This is being run through an Odin platform layer, but I can't imagine that being the problem since these are just passed into vulkan as shader code.

Either way, thank you dearly for all the resources both you and Robin have created regarding Zig on GPU. I will be very happy when this is fully fleshed out. Might try doing GCN for other things in the meantime.

EDIT: I figured out my issue, apparently, GPUs don't like concatenating arrays (++ operator)

@EtienneParmentier
Copy link

EtienneParmentier commented May 13, 2025

EDIT: I figured out my issue, apparently, GPUs don't like concatenating arrays (++ operator)

This is a compile time operation, it should be allowed ? I'd consider this a bug. There are no memory allocator on the GPU.

@EtienneParmentier
Copy link

EtienneParmentier commented May 13, 2025

How does one accesses the compiled shader code ? when using installArtifact on zig 14, it fails because object files can't be installed.

inside: lib\std\Build\Step\InstallArtifact.zig line 56:

    const dest_dir: ?InstallDir = switch (options.dest_dir) {
        .disabled => null,
        .default => switch (artifact.kind) {
            .obj => @panic("object files have no standard installation procedure"),
            .exe, .@"test" => .bin,
            .lib => if (artifact.isDll()) .bin else .lib,
        },
        .override => |o| o,
    };

@VictorSohier
Copy link

I get no such issues on Linux. I suspect it is a bug in the Windows build. I tried this kind of thing on Windows and it basically told me that it has no permission to write.

@alichraghi
Copy link
Author

@EtienneParmentier use getEmittedBin(). example

@EtienneParmentier
Copy link

EtienneParmentier commented May 14, 2025

@EtienneParmentier use getEmittedBin(). example

Cool ! could we update the example at the top ? Actually, I want to save the binary file somewhere, not import it inside another application, to allow swapping shaders after the app is compiled.
I tried using addObjCopy(), but I can't figure out how it works.

I tried using your embedfile approach, and it doesn't work for me either; here is my code (it uses your fragment shader)

Wait it's the same error as @VictorSohier ! I believe submitting an issue to zig repo is valuable at this point, using the shader here and my build.zig code as minimal reproducible example. here it is

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment