Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sokol-gfx compute support #100

Merged
merged 6 commits into from
Mar 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 22 additions & 18 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ pub fn build(b: *Build) !void {
"debugtext-userfont",
"shapes",
"vertexpull",
"instancing-compute",
};
inline for (examples) |example| {
try buildExample(b, example, .{
Expand All @@ -113,7 +114,7 @@ pub fn build(b: *Build) !void {
}

// a manually invoked build step to recompile shaders via sokol-shdc
buildShaders(b, target);
buildShaders(b);
// a manually invoked build step to build auto-docs
buildDocs(b, target);
}
Expand Down Expand Up @@ -506,22 +507,23 @@ fn emSdkSetupStep(b: *Build, emsdk: *Build.Dependency) !?*Build.Step.Run {

// a separate step to compile shaders, expects the shader compiler in ../sokol-tools-bin/
// TODO: install sokol-shdc via package manager
fn buildShaders(b: *Build, target: Build.ResolvedTarget) void {
fn buildShaders(b: *Build) void {
const sokol_tools_bin_dir = "../sokol-tools-bin/bin/";
const shaders_dir = "src/examples/shaders/";
const shaders = .{
"bufferoffsets.glsl",
"cube.glsl",
"instancing.glsl",
"mrt.glsl",
"noninterleaved.glsl",
"offscreen.glsl",
"quad.glsl",
"shapes.glsl",
"texcube.glsl",
"blend.glsl",
"vertexpull.glsl",
"triangle.glsl",
.{ .src = "bufferoffsets.glsl", .needs_compute = false },
.{ .src = "cube.glsl", .needs_compute = false },
.{ .src = "instancing.glsl", .needs_compute = false },
.{ .src = "mrt.glsl", .needs_compute = false },
.{ .src = "noninterleaved.glsl", .needs_compute = false },
.{ .src = "offscreen.glsl", .needs_compute = false },
.{ .src = "quad.glsl", .needs_compute = false },
.{ .src = "shapes.glsl", .needs_compute = false },
.{ .src = "texcube.glsl", .needs_compute = false },
.{ .src = "blend.glsl", .needs_compute = false },
.{ .src = "triangle.glsl", .needs_compute = false },
.{ .src = "vertexpull.glsl", .needs_compute = true },
.{ .src = "instancing-compute.glsl", .needs_compute = true },
};
const optional_shdc: ?[:0]const u8 = comptime switch (builtin.os.tag) {
.windows => "win32/sokol-shdc.exe",
Expand All @@ -535,15 +537,17 @@ fn buildShaders(b: *Build, target: Build.ResolvedTarget) void {
}
const shdc_path = sokol_tools_bin_dir ++ optional_shdc.?;
const shdc_step = b.step("shaders", "Compile shaders (needs ../sokol-tools-bin)");
const glsl = if (isPlatform(target.result, .darwin)) "glsl410" else "glsl430";
const slang = glsl ++ ":metal_macos:hlsl5:glsl300es:wgsl";
inline for (shaders) |shader| {
const slang = if (shader.needs_compute)
"glsl430:glsl310es:metal_macos:hlsl5:wgsl"
else
"glsl410:glsl300es:metal_macos:hlsl5:wgsl";
const cmd = b.addSystemCommand(&.{
shdc_path,
"-i",
shaders_dir ++ shader,
shaders_dir ++ shader.src,
"-o",
shaders_dir ++ shader ++ ".zig",
shaders_dir ++ shader.src ++ ".zig",
"-l",
slang,
"-f",
Expand Down
198 changes: 198 additions & 0 deletions src/examples/instancing-compute.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
//------------------------------------------------------------------------------
// instancing-compute.zig
//
// Like instancing.zig, but update particle positions via compute shader.
//------------------------------------------------------------------------------
const sokol = @import("sokol");
const slog = sokol.log;
const sg = sokol.gfx;
const sapp = sokol.app;
const sglue = sokol.glue;
const vec3 = @import("math.zig").Vec3;
const mat4 = @import("math.zig").Mat4;
const shd = @import("shaders/instancing-compute.glsl.zig");

const max_particles: usize = 512 * 1024;
const num_particles_emitted_per_frame: usize = 10;

const state = struct {
var num_particles: usize = 0;
var ry: f32 = 0.0;
const compute = struct {
var pip: sg.Pipeline = .{};
var bind: sg.Bindings = .{};
};
const display = struct {
var pip: sg.Pipeline = .{};
var bind: sg.Bindings = .{};
var pass_action: sg.PassAction = .{};
const view: mat4 = mat4.lookat(.{ .x = 0, .y = 1.5, .z = 12.0 }, vec3.zero(), vec3.up());
};
};

export fn init() void {
sg.setup(.{
.environment = sglue.environment(),
.logger = .{ .func = slog.func },
});

// if compute shaders not supported, clear to red color and early out
if (!sg.queryFeatures().compute) {
state.display.pass_action.colors[0] = .{
.load_action = .CLEAR,
.clear_value = .{ .r = 1, .g = 0, .b = 0, .a = 1 },
};
return;
}

// regular clear color
state.display.pass_action.colors[0] = .{
.load_action = .CLEAR,
.clear_value = .{ .r = 0, .g = 0.1, .b = 0.2, .a = 1 },
};

// a zero-initialized storage buffer for the particle state
const sbuf = sg.makeBuffer(.{
.type = .STORAGEBUFFER,
.size = max_particles * @sizeOf(shd.Particle),
.label = "particle-buffer",
});
state.compute.bind.storage_buffers[shd.SBUF_cs_ssbo] = sbuf;
state.display.bind.storage_buffers[shd.SBUF_vs_ssbo] = sbuf;

// a compute shader and pipeline object for updating the particle state
state.compute.pip = sg.makePipeline(.{
.compute = true,
.shader = sg.makeShader(shd.updateShaderDesc(sg.queryBackend())),
.label = "update-pipeline",
});

// vertex and index buffer for particle geometry
const r = 0.05;
state.display.bind.vertex_buffers[0] = sg.makeBuffer(.{
.data = sg.asRange(&[_]f32{
0.0, -r, 0.0, 1.0, 0.0, 0.0, 1.0,
r, 0.0, r, 0.0, 1.0, 0.0, 1.0,
r, 0.0, -r, 0.0, 0.0, 1.0, 1.0,
-r, 0.0, -r, 1.0, 1.0, 0.0, 1.0,
-r, 0.0, r, 0.0, 1.0, 1.0, 1.0,
0.0, r, 0.0, 1.0, 0.0, 1.0, 1.0,
}),
.label = "geometry-vbuf",
});
state.display.bind.index_buffer = sg.makeBuffer(.{
.type = .INDEXBUFFER,
.data = sg.asRange(&[_]u16{
2, 1, 0, 3, 2, 0,
4, 3, 0, 1, 4, 0,
5, 1, 2, 5, 2, 3,
5, 3, 4, 5, 4, 1,
}),
.label = "geometry-ibuf",
});

// shader and pipeline for rendering the particles, this uses
// the compute-updated storage buffer to provide the particle positions
state.display.pip = sg.makePipeline(.{
.shader = sg.makeShader(shd.displayShaderDesc(sg.queryBackend())),
.layout = brk: {
var layout: sg.VertexLayoutState = .{};
layout.attrs[0] = .{ .format = .FLOAT3 };
layout.attrs[1] = .{ .format = .FLOAT4 };
break :brk layout;
},
.index_type = .UINT16,
.depth = .{
.compare = .LESS_EQUAL,
.write_enabled = true,
},
.cull_mode = .BACK,
.label = "render-pipeline",
});

// one-time init of particle velocities via a compute shader
const pip = sg.makePipeline(.{
.compute = true,
.shader = sg.makeShader(shd.initShaderDesc(sg.queryBackend())),
});
sg.beginPass(.{ .compute = true });
sg.applyPipeline(pip);
sg.applyBindings(state.compute.bind);
sg.dispatch(max_particles / 64, 1, 1);
sg.endPass();
sg.destroyPipeline(pip);
}

export fn frame() void {
if (!sg.queryFeatures().compute) {
drawFallback();
return;
}

state.num_particles += num_particles_emitted_per_frame;
if (state.num_particles > max_particles) {
state.num_particles = max_particles;
}
const dt: f32 = @floatCast(sapp.frameDuration());

// compute pass to update particle positions
const cs_params: shd.CsParams = .{
.dt = dt,
.num_particles = @intCast(state.num_particles),
};
sg.beginPass(.{ .compute = true, .label = "compute-pass" });
sg.applyPipeline(state.compute.pip);
sg.applyBindings(state.compute.bind);
sg.applyUniforms(shd.UB_cs_params, sg.asRange(&cs_params));
sg.dispatch(@intCast((state.num_particles + 63) / 64), 1, 1);
sg.endPass();

// render pass to render the particles via instancing, with the
// instance positions coming from the storage buffer
state.ry += 60.0 * dt;
const vs_params = computeVsParams(1.0, state.ry);
sg.beginPass(.{
.action = state.display.pass_action,
.swapchain = sglue.swapchain(),
.label = "render-pass",
});
sg.applyPipeline(state.display.pip);
sg.applyBindings(state.display.bind);
sg.applyUniforms(shd.UB_vs_params, sg.asRange(&vs_params));
sg.draw(0, 24, @intCast(state.num_particles));
sg.endPass();
sg.commit();
}

export fn cleanup() void {
sg.shutdown();
}

pub fn main() void {
sapp.run(.{
.init_cb = init,
.frame_cb = frame,
.cleanup_cb = cleanup,
.width = 800,
.height = 600,
.sample_count = 4,
.icon = .{ .sokol_default = true },
.window_title = "instancing-compute.zig",
.logger = .{ .func = slog.func },
});
}

fn computeVsParams(rx: f32, ry: f32) shd.VsParams {
const rxm = mat4.rotate(rx, .{ .x = 1.0, .y = 0.0, .z = 0.0 });
const rym = mat4.rotate(ry, .{ .x = 0.0, .y = 1.0, .z = 0.0 });
const model = mat4.mul(rxm, rym);
const aspect = sapp.widthf() / sapp.heightf();
const proj = mat4.persp(60.0, aspect, 0.01, 50.0);
return shd.VsParams{ .mvp = mat4.mul(mat4.mul(proj, state.display.view), model) };
}

fn drawFallback() void {
sg.beginPass(.{ .action = state.display.pass_action, .swapchain = sglue.swapchain(), .label = "render-pass" });
sg.endPass();
sg.commit();
}
12 changes: 5 additions & 7 deletions src/examples/shaders/blend.glsl.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const m = @import("../math.zig");
// Generated by sokol-shdc (https://github.com/floooh/sokol-tools)
//
// Cmdline:
// sokol-shdc -i src/examples/shaders/blend.glsl -o src/examples/shaders/blend.glsl.zig -l glsl410:metal_macos:hlsl5:glsl300es:wgsl -f sokol_zig --reflection
// sokol-shdc -i src/examples/shaders/blend.glsl -o src/examples/shaders/blend.glsl.zig -l glsl410:glsl300es:metal_macos:hlsl5:wgsl -f sokol_zig --reflection
//
// Overview:
// =========
Expand Down Expand Up @@ -1269,12 +1269,11 @@ pub fn bgUniformOffset(ub_name: []const u8, u_name: []const u8) ?usize {
}
return null;
}
pub fn bgUniformDesc(ub_name: []const u8, u_name: []const u8) ?sg.GlslShaderUniformDesc {
pub fn bgUniformDesc(ub_name: []const u8, u_name: []const u8) ?sg.GlslShaderUniform {
if (std.mem.eql(u8, ub_name, "bg_fs_params")) {
if (std.mem.eql(u8, u_name, "tick")) {
var desc: sg.ShaderUniformDesc = .{};
var desc: sg.GlslShaderUniform = .{};
desc.type = .FLOAT;
desc.offset = 0;
desc.array_count = 0;
desc.glsl_name = "tick";
return desc;
Expand Down Expand Up @@ -1323,12 +1322,11 @@ pub fn quadUniformOffset(ub_name: []const u8, u_name: []const u8) ?usize {
}
return null;
}
pub fn quadUniformDesc(ub_name: []const u8, u_name: []const u8) ?sg.GlslShaderUniformDesc {
pub fn quadUniformDesc(ub_name: []const u8, u_name: []const u8) ?sg.GlslShaderUniform {
if (std.mem.eql(u8, ub_name, "quad_vs_params")) {
if (std.mem.eql(u8, u_name, "mvp")) {
var desc: sg.ShaderUniformDesc = .{};
var desc: sg.GlslShaderUniform = .{};
desc.type = .MAT4;
desc.offset = 0;
desc.array_count = 0;
desc.glsl_name = "mvp";
return desc;
Expand Down
4 changes: 2 additions & 2 deletions src/examples/shaders/bufferoffsets.glsl.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const std = @import("std");
// Generated by sokol-shdc (https://github.com/floooh/sokol-tools)
//
// Cmdline:
// sokol-shdc -i src/examples/shaders/bufferoffsets.glsl -o src/examples/shaders/bufferoffsets.glsl.zig -l glsl410:metal_macos:hlsl5:glsl300es:wgsl -f sokol_zig --reflection
// sokol-shdc -i src/examples/shaders/bufferoffsets.glsl -o src/examples/shaders/bufferoffsets.glsl.zig -l glsl410:glsl300es:metal_macos:hlsl5:wgsl -f sokol_zig --reflection
//
// Overview:
// =========
Expand Down Expand Up @@ -572,7 +572,7 @@ pub fn bufferoffsetsUniformOffset(ub_name: []const u8, u_name: []const u8) ?usiz
_ = u_name;
return null;
}
pub fn bufferoffsetsUniformDesc(ub_name: []const u8, u_name: []const u8) ?sg.GlslShaderUniformDesc {
pub fn bufferoffsetsUniformDesc(ub_name: []const u8, u_name: []const u8) ?sg.GlslShaderUniform {
_ = ub_name;
_ = u_name;
return null;
Expand Down
7 changes: 3 additions & 4 deletions src/examples/shaders/cube.glsl.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const m = @import("../math.zig");
// Generated by sokol-shdc (https://github.com/floooh/sokol-tools)
//
// Cmdline:
// sokol-shdc -i src/examples/shaders/cube.glsl -o src/examples/shaders/cube.glsl.zig -l glsl410:metal_macos:hlsl5:glsl300es:wgsl -f sokol_zig --reflection
// sokol-shdc -i src/examples/shaders/cube.glsl -o src/examples/shaders/cube.glsl.zig -l glsl410:glsl300es:metal_macos:hlsl5:wgsl -f sokol_zig --reflection
//
// Overview:
// =========
Expand Down Expand Up @@ -662,12 +662,11 @@ pub fn cubeUniformOffset(ub_name: []const u8, u_name: []const u8) ?usize {
}
return null;
}
pub fn cubeUniformDesc(ub_name: []const u8, u_name: []const u8) ?sg.GlslShaderUniformDesc {
pub fn cubeUniformDesc(ub_name: []const u8, u_name: []const u8) ?sg.GlslShaderUniform {
if (std.mem.eql(u8, ub_name, "vs_params")) {
if (std.mem.eql(u8, u_name, "mvp")) {
var desc: sg.ShaderUniformDesc = .{};
var desc: sg.GlslShaderUniform = .{};
desc.type = .MAT4;
desc.offset = 0;
desc.array_count = 0;
desc.glsl_name = "mvp";
return desc;
Expand Down
Loading
Loading