a small helper library for zig shaders. Features:
- automated location binding
- allows comptime analysis of gpu shaders from a cpu target (aka pipeline compatibility checks)
1 messages · Page 1 of 1 (latest)
a small helper library for zig shaders. Features:
zig-gpu: a small helper library for zig shaders
example of a zig shader using this lib:
const gpu = @import("gpu");
pub const uniform = gpu.ptr(@This(), struct { time: f32 }, .uniform, "uniform");
pub const xy = gpu.ptr(@This(), @Vector(2, f32), .input, "xy");
pub const color = gpu.ptr(@This(), @Vector(4, f32), .output, "color");
fn main() void {
const x: f32 = xy[0];
const y: f32 = xy[1];
const t_max = 5.0;
const rr_max = 2.0;
const t: f32 = @mod(uniform.time, t_max) / t_max;
if (x * x + y * y < rr_max * t * t) {
color.* = .{ 1, t, t, 1 };
} else {
color.* = .{ 1, 1, 1, 1 };
}
color.* *= .{ 0.9, 0.9, 0.9, 1 };
}
comptime {
gpu.main(@This(), main, .spirv_fragment);
}
It can be compiled to spirv for a gpu target and imported when compiling to a cpu target to perform unit tests like pipeline checks:
test "compatibility check" {
const Canvas = @import("shaders/canvas.vert.zig");
const Color = @import("shaders/color.frag.zig");
gpu.Check.render(Canvas, Color);
}
have you considered having pointers generated from input/outputs? This would enforce usage of inputs and writing of outputs. Something like:
const gpu = @import("gpu");
const Outputs = struct {
color: @Vector(4, f32),
};
fn main(
xy: @Vector(2, f32),
) Outputs {
const x: f32 = xy[0];
const y: f32 = xy[1];
const color = if (x * x + y * y < 0.1)
.{ 0.9, 0, 0, 1 }
else
.{ 0.9, 0.9, 0.9, 1 };
return .{
.color = color,
};
}
comptime {
gpu.main(@This(), main, .spirv_fragment);
}
if gpu.main takes the function as anytype, you could probably do some trickery to generate the pointers. From the inputs to the function, you can generate a struct for it
Awesome idea :D
I will definetly give it a try
How would this work with other address spaces than input and output? 🤔
I don't know
Maybe just do uniforms the current way as they kinda act like globals anyways?
added support for uniforms
added helpers functions for number of buffers and their locations. So one can do (using zig-sdl3 as an example):
const ShaderFrag = @import("shaders/color.frag.zig");
// pipeline creation:
shader_frag = try device.createShader(.{
///...
.num_uniform_buffers = gpu.decls(ShaderFrag, .uniform).len,
//...
});
// updating buffers:
command_buffer.pushFragmentUniformData(gpu.location(ShaderFrag.uniform), bytes);
it should also be pretty easy to type check the bytes of the buffer push.
not quite, this is a very thin layer on top of std.gpu. I hope its usefulness becomes more apparent with the new functions i just described above :)