Skip to content

Commit

Permalink
Merge pull request #100 from floooh/sgcompute
Browse files Browse the repository at this point in the history
sokol-gfx compute support
  • Loading branch information
floooh authored Mar 8, 2025
2 parents b8f3e5a + 74ebb6f commit 96c52be
Show file tree
Hide file tree
Showing 19 changed files with 4,753 additions and 1,021 deletions.
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 @@ -508,22 +509,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 @@ -537,15 +539,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

0 comments on commit 96c52be

Please sign in to comment.