/// 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 core = mach.core; const gpu = core.gpu; title_timer: core.Timer, timer: core.Timer, 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, pub const App = @This(); pub const mach_core_options = core.ComptimeOptions{ .use_wgpu = false, .use_sysgpu = true, }; var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 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) !void { try core.init(.{}); const sprite_shader_module = core.device.createShaderModuleWGSL( "sprite.wgsl", @embedFile("sprite.wgsl"), ); defer sprite_shader_module.release(); const update_sprite_shader_module = core.device.createShaderModuleWGSL( "updateSprites.wgsl", @embedFile("updateSprites.wgsl"), ); defer update_sprite_shader_module.release(); 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 = core.device.createRenderPipeline(&gpu.RenderPipeline.Descriptor{ .vertex = gpu.VertexState.init(.{ .module = sprite_shader_module, .entry_point = "vert_main", .buffers = &.{ gpu.VertexBufferLayout.init(.{ // instanced particles buffer .array_stride = 4 * 4, .step_mode = .instance, .attributes = &instanced_particles_attributes, }), gpu.VertexBufferLayout.init(.{ // vertex buffer .array_stride = 2 * 4, .step_mode = .vertex, .attributes = &vertex_buffer_attributes, }), }, }), .fragment = &gpu.FragmentState.init(.{ .module = sprite_shader_module, .entry_point = "frag_main", .targets = &[_]gpu.ColorTargetState{.{ .format = core.descriptor.format, }}, }), }); const compute_pipeline = core.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 = core.device.createBuffer(&gpu.Buffer.Descriptor{ .label = "sprite_vertex_buffer", .usage = .{ .vertex = true }, .mapped_at_creation = .true, .size = vert_buffer_data.len * @sizeOf(f32), }); const vertex_mapped = sprite_vertex_buffer.getMappedRange(f32, 0, vert_buffer_data.len); @memcpy(vertex_mapped.?, vert_buffer_data[0..]); sprite_vertex_buffer.unmap(); const sim_param_buffer = core.device.createBuffer(&gpu.Buffer.Descriptor{ .label = "sim_param_buffer", .usage = .{ .uniform = true, .copy_dst = true }, .size = sim_params.len * @sizeOf(f32), }); core.queue.writeBuffer(sim_param_buffer, 0, sim_params[0..]); 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] = core.device.createBuffer(&gpu.Buffer.Descriptor{ .label = "particle_buffer", .mapped_at_creation = .true, .usage = .{ .vertex = true, .storage = true, }, .size = initial_particle_data.len * @sizeOf(f32), }); const mapped = particle_buffers[i].getMappedRange(f32, 0, initial_particle_data.len); @memcpy(mapped.?, initial_particle_data[0..]); particle_buffers[i].unmap(); } i = 0; while (i < 2) : (i += 1) { const layout = compute_pipeline.getBindGroupLayout(0); defer layout.release(); particle_bind_groups[i] = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{ .layout = layout, .entries = &.{ gpu.BindGroup.Entry.buffer(0, sim_param_buffer, 0, sim_params.len * @sizeOf(f32), sim_params.len * @sizeOf(f32)), gpu.BindGroup.Entry.buffer(1, particle_buffers[i], 0, initial_particle_data.len * @sizeOf(f32), 4 * @sizeOf(f32)), gpu.BindGroup.Entry.buffer(2, particle_buffers[(i + 1) % 2], 0, initial_particle_data.len * @sizeOf(f32), 4 * @sizeOf(f32)), }, })); } app.* = .{ .timer = try core.Timer.start(), .title_timer = try core.Timer.start(), .compute_pipeline = compute_pipeline, .render_pipeline = render_pipeline, .sprite_vertex_buffer = sprite_vertex_buffer, .particle_buffers = particle_buffers, .particle_bind_groups = particle_bind_groups, .sim_param_buffer = sim_param_buffer, .frame_counter = 0, }; } pub fn deinit(app: *App) void { defer _ = gpa.deinit(); defer core.deinit(); app.compute_pipeline.release(); app.render_pipeline.release(); app.sprite_vertex_buffer.release(); for (app.particle_buffers) |particle_buffer| particle_buffer.release(); for (app.particle_bind_groups) |particle_bind_group| particle_bind_group.release(); app.sim_param_buffer.release(); } pub fn update(app: *App) !bool { const delta_time = app.timer.lap(); var iter = core.pollEvents(); while (iter.next()) |event| { if (event == .close) return true; } const back_buffer_view = core.swap_chain.getCurrentTextureView().?; const color_attachment = gpu.RenderPassColorAttachment{ .view = back_buffer_view, .clear_value = std.mem.zeroes(gpu.Color), .load_op = .clear, .store_op = .store, }; const render_pass_descriptor = gpu.RenderPassDescriptor.init(.{ .color_attachments = &.{ color_attachment, }, }); sim_params[0] = @as(f32, @floatCast(delta_time)); core.queue.writeBuffer(app.sim_param_buffer, 0, sim_params[0..]); const command_encoder = core.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.dispatchWorkgroups(@as(u32, @intFromFloat(@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(); core.queue.submit(&[_]*gpu.CommandBuffer{command}); command.release(); core.swap_chain.present(); back_buffer_view.release(); // update the window title every second if (app.title_timer.read() >= 1.0) { app.title_timer.reset(); try core.printTitle("Boids [ {d}fps ] [ Input {d}hz ]", .{ core.frameRate(), core.inputRate(), }); } return false; }