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.binding(&ubo, 0, 0);
gpu.location(&frag_color, 0);
frag_color = ubo.object_color * ubo.light_color;
}
In CLI:
zig build-obj shader.zig -target spirv32-vulkan -ofmt=spirv -mcpu vulkan_v1_2 -fno-llvm
In build.zig
:
const vulkan12_target = b.resolveTargetQuery(.{
.cpu_arch = .spirv32,
.cpu_model = .{ .explicit = &std.Target.spirv.cpu.vulkan_v1_2 },
.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()`
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_in/gpu.position_out |
gl_VertexIndex |
SV_VertexID |
gpu.vertex_index |
gl_InstanceIndex |
SV_InstanceID |
gpu.instance_index |
gl_FragCoord |
SV_Position |
gpu.fragment_coord |
gl_FragDepth |
SV_Depth |
gpu.fragment_depth |
layout(location=N) |
SV_Target |
gpu.location() |
layout(binding=N) |
register() |
gpu.binding() |
gl_GlobalInvocationID |
SV_DispatchThreadID |
gpu.global_invocation_id |
gl_LocalInvocationID |
SV_GroupThreadID |
gpu.local_invocation_id |
You can directly write SPIR-V assembly using the inline assembly feature. As you probably have noitced, it requires 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
.
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.
If you have any questions, feel free to reach me (#alichraghi
) or
Snektron (#snektron
) in Discord or ZSF's zulip.
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