std.debug.print uses IO which depends on file system and thus needs support from OS, which dont have in freestanding targets (like WASM).
210 lines
7.8 KiB
Zig
210 lines
7.8 KiB
Zig
/// A port of Austin Eng's "computeBoids" webgpu sample.
|
|
/// https://github.com/austinEng/webgpu-samples/blob/main/src/sample/computeBoids/main.ts
|
|
const std = @import("std");
|
|
const mach = @import("mach");
|
|
const gpu = @import("gpu");
|
|
|
|
compute_pipeline: gpu.ComputePipeline,
|
|
render_pipeline: gpu.RenderPipeline,
|
|
sprite_vertex_buffer: gpu.Buffer,
|
|
particle_buffers: [2]gpu.Buffer,
|
|
particle_bind_groups: [2]gpu.BindGroup,
|
|
sim_param_buffer: gpu.Buffer,
|
|
frame_counter: usize,
|
|
|
|
const App = @This();
|
|
|
|
const num_particle = 1500;
|
|
|
|
var sim_params = [_]f32{
|
|
0.04, // .delta_T
|
|
0.1, // .rule_1_distance
|
|
0.025, // .rule_2_distance
|
|
0.025, // .rule_3_distance
|
|
0.02, // .rule_1_scale
|
|
0.05, // .rule_2_scale
|
|
0.005, // .rule_3_scale
|
|
};
|
|
|
|
pub fn init(app: *App, engine: *mach.Engine) !void {
|
|
const sprite_shader_module = engine.gpu_driver.device.createShaderModule(&.{
|
|
.label = "sprite shader module",
|
|
.code = .{ .wgsl = @embedFile("sprite.wgsl") },
|
|
});
|
|
|
|
const update_sprite_shader_module = engine.gpu_driver.device.createShaderModule(&.{
|
|
.label = "update sprite shader module",
|
|
.code = .{ .wgsl = @embedFile("updateSprites.wgsl") },
|
|
});
|
|
|
|
const instanced_particles_attributes = [_]gpu.VertexAttribute{
|
|
.{
|
|
// instance position
|
|
.shader_location = 0,
|
|
.offset = 0,
|
|
.format = .float32x2,
|
|
},
|
|
.{
|
|
// instance velocity
|
|
.shader_location = 1,
|
|
.offset = 2 * 4,
|
|
.format = .float32x2,
|
|
},
|
|
};
|
|
|
|
const vertex_buffer_attributes = [_]gpu.VertexAttribute{
|
|
.{
|
|
// vertex positions
|
|
.shader_location = 2,
|
|
.offset = 0,
|
|
.format = .float32x2,
|
|
},
|
|
};
|
|
|
|
const render_pipeline = engine.gpu_driver.device.createRenderPipeline(&gpu.RenderPipeline.Descriptor{
|
|
.vertex = .{
|
|
.module = sprite_shader_module,
|
|
.entry_point = "vert_main",
|
|
.buffers = &[_]gpu.VertexBufferLayout{
|
|
.{
|
|
// instanced particles buffer
|
|
.array_stride = 4 * 4,
|
|
.step_mode = .instance,
|
|
.attribute_count = instanced_particles_attributes.len,
|
|
.attributes = &instanced_particles_attributes,
|
|
},
|
|
.{
|
|
// vertex buffer
|
|
.array_stride = 2 * 4,
|
|
.step_mode = .vertex,
|
|
.attribute_count = vertex_buffer_attributes.len,
|
|
.attributes = &vertex_buffer_attributes,
|
|
},
|
|
},
|
|
},
|
|
.fragment = &gpu.FragmentState{ .module = sprite_shader_module, .entry_point = "frag_main", .targets = &[_]gpu.ColorTargetState{
|
|
.{
|
|
.format = engine.gpu_driver.swap_chain_format,
|
|
},
|
|
} },
|
|
});
|
|
|
|
const compute_pipeline = engine.gpu_driver.device.createComputePipeline(&gpu.ComputePipeline.Descriptor{ .compute = gpu.ProgrammableStageDescriptor{
|
|
.module = update_sprite_shader_module,
|
|
.entry_point = "main",
|
|
} });
|
|
|
|
const vert_buffer_data = [_]f32{
|
|
-0.01, -0.02, 0.01,
|
|
-0.02, 0.0, 0.02,
|
|
};
|
|
|
|
const sprite_vertex_buffer = engine.gpu_driver.device.createBuffer(&gpu.Buffer.Descriptor{
|
|
.usage = .{ .vertex = true, .copy_dst = true },
|
|
.size = vert_buffer_data.len * @sizeOf(f32),
|
|
});
|
|
engine.gpu_driver.device.getQueue().writeBuffer(sprite_vertex_buffer, 0, f32, &vert_buffer_data);
|
|
|
|
const sim_param_buffer = engine.gpu_driver.device.createBuffer(&gpu.Buffer.Descriptor{
|
|
.usage = .{ .uniform = true, .copy_dst = true },
|
|
.size = sim_params.len * @sizeOf(f32),
|
|
});
|
|
engine.gpu_driver.device.getQueue().writeBuffer(sim_param_buffer, 0, f32, &sim_params);
|
|
|
|
var initial_particle_data: [num_particle * 4]f32 = undefined;
|
|
var rng = std.rand.DefaultPrng.init(0);
|
|
const random = rng.random();
|
|
var i: usize = 0;
|
|
while (i < num_particle) : (i += 1) {
|
|
initial_particle_data[4 * i + 0] = 2 * (random.float(f32) - 0.5);
|
|
initial_particle_data[4 * i + 1] = 2 * (random.float(f32) - 0.5);
|
|
initial_particle_data[4 * i + 2] = 2 * (random.float(f32) - 0.5) * 0.1;
|
|
initial_particle_data[4 * i + 3] = 2 * (random.float(f32) - 0.5) * 0.1;
|
|
}
|
|
|
|
var particle_buffers: [2]gpu.Buffer = undefined;
|
|
var particle_bind_groups: [2]gpu.BindGroup = undefined;
|
|
i = 0;
|
|
while (i < 2) : (i += 1) {
|
|
particle_buffers[i] = engine.gpu_driver.device.createBuffer(&gpu.Buffer.Descriptor{
|
|
.usage = .{
|
|
.vertex = true,
|
|
.copy_dst = true,
|
|
.storage = true,
|
|
},
|
|
.size = initial_particle_data.len * @sizeOf(f32),
|
|
});
|
|
engine.gpu_driver.device.getQueue().writeBuffer(particle_buffers[i], 0, f32, &initial_particle_data);
|
|
}
|
|
|
|
i = 0;
|
|
while (i < 2) : (i += 1) {
|
|
particle_bind_groups[i] = engine.gpu_driver.device.createBindGroup(&gpu.BindGroup.Descriptor{ .layout = compute_pipeline.getBindGroupLayout(0), .entries = &[_]gpu.BindGroup.Entry{
|
|
gpu.BindGroup.Entry.buffer(0, sim_param_buffer, 0, sim_params.len * @sizeOf(f32)),
|
|
gpu.BindGroup.Entry.buffer(1, particle_buffers[i], 0, initial_particle_data.len * @sizeOf(f32)),
|
|
gpu.BindGroup.Entry.buffer(2, particle_buffers[(i + 1) % 2], 0, initial_particle_data.len * @sizeOf(f32)),
|
|
} });
|
|
}
|
|
|
|
app.compute_pipeline = compute_pipeline;
|
|
app.render_pipeline = render_pipeline;
|
|
app.sprite_vertex_buffer = sprite_vertex_buffer;
|
|
app.particle_buffers = particle_buffers;
|
|
app.particle_bind_groups = particle_bind_groups;
|
|
app.sim_param_buffer = sim_param_buffer;
|
|
app.frame_counter = 0;
|
|
}
|
|
|
|
pub fn deinit(_: *App, _: *mach.Engine) void {}
|
|
|
|
pub fn update(app: *App, engine: *mach.Engine) !bool {
|
|
const back_buffer_view = engine.gpu_driver.swap_chain.?.getCurrentTextureView();
|
|
const color_attachment = gpu.RenderPassColorAttachment{
|
|
.view = back_buffer_view,
|
|
.resolve_target = null,
|
|
.clear_value = std.mem.zeroes(gpu.Color),
|
|
.load_op = .clear,
|
|
.store_op = .store,
|
|
};
|
|
|
|
const render_pass_descriptor = gpu.RenderPassEncoder.Descriptor{ .color_attachments = &[_]gpu.RenderPassColorAttachment{
|
|
color_attachment,
|
|
} };
|
|
|
|
sim_params[0] = @floatCast(f32, engine.delta_time);
|
|
engine.gpu_driver.device.getQueue().writeBuffer(app.sim_param_buffer, 0, f32, &sim_params);
|
|
|
|
const command_encoder = engine.gpu_driver.device.createCommandEncoder(null);
|
|
{
|
|
const pass_encoder = command_encoder.beginComputePass(null);
|
|
pass_encoder.setPipeline(app.compute_pipeline);
|
|
pass_encoder.setBindGroup(0, app.particle_bind_groups[app.frame_counter % 2], null);
|
|
pass_encoder.dispatch(@floatToInt(u32, @ceil(@as(f32, num_particle) / 64)), 1, 1);
|
|
pass_encoder.end();
|
|
pass_encoder.release();
|
|
}
|
|
{
|
|
const pass_encoder = command_encoder.beginRenderPass(&render_pass_descriptor);
|
|
pass_encoder.setPipeline(app.render_pipeline);
|
|
pass_encoder.setVertexBuffer(0, app.particle_buffers[(app.frame_counter + 1) % 2], 0, num_particle * 4 * @sizeOf(f32));
|
|
pass_encoder.setVertexBuffer(1, app.sprite_vertex_buffer, 0, 6 * @sizeOf(f32));
|
|
pass_encoder.draw(3, num_particle, 0, 0);
|
|
pass_encoder.end();
|
|
pass_encoder.release();
|
|
}
|
|
|
|
app.frame_counter += 1;
|
|
if (app.frame_counter % 60 == 0) {
|
|
std.log.info("Frame {}", .{app.frame_counter});
|
|
}
|
|
|
|
var command = command_encoder.finish(null);
|
|
command_encoder.release();
|
|
engine.gpu_driver.device.getQueue().submit(&.{command});
|
|
command.release();
|
|
|
|
engine.gpu_driver.swap_chain.?.present();
|
|
back_buffer_view.release();
|
|
|
|
return true;
|
|
}
|