mirror of
https://github.com/gfx-rs/wgpu.git
synced 2026-04-22 03:02:01 -04:00
[rs] Merge #144
144: Adds compute-boids example from gpuweb demos r=kvark a=masonblier Port of the computeBoids.ts demo from https://github.com/austinEng/webgpu-samples/blob/master/src/examples/computeBoids.ts using separate compute and render passes with shared buffers bound by layout location.  I'm happy to receive any feedback related to these changes and if there's any interest in merging it to the main repository. Thanks, Mason Co-authored-by: Mason <mason.blier@gmail.com>
This commit is contained in:
@@ -45,5 +45,6 @@ glsl-to-spirv = "0.1"
|
||||
log = "0.4"
|
||||
png = "0.15"
|
||||
winit = "0.20.0-alpha4"
|
||||
rand = "0.7.2"
|
||||
zerocopy = "0.2"
|
||||
futures = "0.3"
|
||||
|
||||
77
wgpu/examples/boids/boids.comp
Normal file
77
wgpu/examples/boids/boids.comp
Normal file
@@ -0,0 +1,77 @@
|
||||
#version 450
|
||||
|
||||
// this shader expects NUM_PARTICLES and PARTICLES_PER_GROUP
|
||||
// defines to be inserted by main.rs
|
||||
|
||||
layout(local_size_x = PARTICLES_PER_GROUP) in;
|
||||
|
||||
struct Particle {
|
||||
vec2 pos;
|
||||
vec2 vel;
|
||||
};
|
||||
layout(std140, set = 0, binding = 0) uniform SimParams {
|
||||
float deltaT;
|
||||
float rule1Distance;
|
||||
float rule2Distance;
|
||||
float rule3Distance;
|
||||
float rule1Scale;
|
||||
float rule2Scale;
|
||||
float rule3Scale;
|
||||
} params;
|
||||
layout(std140, set = 0, binding = 1) buffer SrcParticles {
|
||||
Particle particles[NUM_PARTICLES];
|
||||
} srcParticles;
|
||||
layout(std140, set = 0, binding = 2) buffer DstParticles {
|
||||
Particle particles[NUM_PARTICLES];
|
||||
} dstParticles;
|
||||
|
||||
void main() {
|
||||
// https://github.com/austinEng/Project6-Vulkan-Flocking/blob/master/data/shaders/computeparticles/particle.comp
|
||||
uint index = gl_GlobalInvocationID.x;
|
||||
|
||||
if (index >= NUM_PARTICLES) { return; }
|
||||
vec2 vPos = srcParticles.particles[index].pos;
|
||||
vec2 vVel = srcParticles.particles[index].vel;
|
||||
vec2 cMass = vec2(0.0, 0.0);
|
||||
vec2 cVel = vec2(0.0, 0.0);
|
||||
vec2 colVel = vec2(0.0, 0.0);
|
||||
int cMassCount = 0;
|
||||
int cVelCount = 0;
|
||||
vec2 pos;
|
||||
vec2 vel;
|
||||
for (int i = 0; i < NUM_PARTICLES; ++i) {
|
||||
if (i == index) { continue; }
|
||||
pos = srcParticles.particles[i].pos.xy;
|
||||
vel = srcParticles.particles[i].vel.xy;
|
||||
if (distance(pos, vPos) < params.rule1Distance) {
|
||||
cMass += pos;
|
||||
cMassCount++;
|
||||
}
|
||||
if (distance(pos, vPos) < params.rule2Distance) {
|
||||
colVel -= (pos - vPos);
|
||||
}
|
||||
if (distance(pos, vPos) < params.rule3Distance) {
|
||||
cVel += vel;
|
||||
cVelCount++;
|
||||
}
|
||||
}
|
||||
if (cMassCount > 0) {
|
||||
cMass = cMass / cMassCount - vPos;
|
||||
}
|
||||
if (cVelCount > 0) {
|
||||
cVel = cVel / cVelCount;
|
||||
}
|
||||
vVel += cMass * params.rule1Scale + colVel * params.rule2Scale + cVel * params.rule3Scale;
|
||||
// clamp velocity for a more pleasing simulation.
|
||||
vVel = normalize(vVel) * clamp(length(vVel), 0.0, 0.1);
|
||||
// kinematic update
|
||||
vPos += vVel * params.deltaT;
|
||||
// Wrap around boundary
|
||||
if (vPos.x < -1.0) vPos.x = 1.0;
|
||||
if (vPos.x > 1.0) vPos.x = -1.0;
|
||||
if (vPos.y < -1.0) vPos.y = 1.0;
|
||||
if (vPos.y > 1.0) vPos.y = -1.0;
|
||||
dstParticles.particles[index].pos = vPos;
|
||||
// Write back
|
||||
dstParticles.particles[index].vel = vVel;
|
||||
}
|
||||
341
wgpu/examples/boids/main.rs
Normal file
341
wgpu/examples/boids/main.rs
Normal file
@@ -0,0 +1,341 @@
|
||||
// Flocking boids example with gpu compute update pass
|
||||
// adapted from https://github.com/austinEng/webgpu-samples/blob/master/src/examples/computeBoids.ts
|
||||
|
||||
extern crate rand;
|
||||
|
||||
#[path = "../framework.rs"]
|
||||
mod framework;
|
||||
|
||||
use zerocopy::{AsBytes};
|
||||
|
||||
|
||||
// number of boid particles to simulate
|
||||
|
||||
const NUM_PARTICLES: u32 = 1500;
|
||||
|
||||
// number of single-particle calculations (invocations) in each gpu work group
|
||||
|
||||
const PARTICLES_PER_GROUP: u32 = 64;
|
||||
|
||||
|
||||
/// Example struct holds references to wgpu resources and frame persistent data
|
||||
struct Example {
|
||||
particle_bind_groups: Vec<wgpu::BindGroup>,
|
||||
particle_buffers: Vec<wgpu::Buffer>,
|
||||
vertices_buffer: wgpu::Buffer,
|
||||
compute_pipeline: wgpu::ComputePipeline,
|
||||
render_pipeline: wgpu::RenderPipeline,
|
||||
work_group_count: u32,
|
||||
frame_num: usize,
|
||||
}
|
||||
|
||||
|
||||
impl framework::Example for Example {
|
||||
|
||||
/// constructs initial instance of Example struct
|
||||
fn init(
|
||||
sc_desc: &wgpu::SwapChainDescriptor,
|
||||
device: &wgpu::Device,
|
||||
) -> (Self, Option<wgpu::CommandBuffer>) {
|
||||
|
||||
// loads comp shader source and adds shared constants as defines to comp shader
|
||||
|
||||
let mut boids_source_str = String::from(include_str!("boids.comp"));
|
||||
let version_header_str = "#version 450\n";
|
||||
assert!(boids_source_str.starts_with(version_header_str));
|
||||
boids_source_str.insert_str(version_header_str.len(),
|
||||
&format!("#define NUM_PARTICLES {}\n#define PARTICLES_PER_GROUP {}\n",
|
||||
NUM_PARTICLES, PARTICLES_PER_GROUP));
|
||||
|
||||
|
||||
// load (and compile) shaders and create shader modules
|
||||
|
||||
let boids = framework::load_glsl(&boids_source_str, framework::ShaderStage::Compute);
|
||||
let boids_module = device.create_shader_module(&boids);
|
||||
|
||||
let vs = framework::load_glsl(include_str!("shader.vert"), framework::ShaderStage::Vertex);
|
||||
let vs_module = device.create_shader_module(&vs);
|
||||
|
||||
let fs = framework::load_glsl(include_str!("shader.frag"), framework::ShaderStage::Fragment);
|
||||
let fs_module = device.create_shader_module(&fs);
|
||||
|
||||
|
||||
// create compute bind layout group and compute pipeline layout
|
||||
|
||||
let compute_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
bindings: &[
|
||||
wgpu::BindGroupLayoutBinding {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStage::COMPUTE,
|
||||
ty: wgpu::BindingType::UniformBuffer { dynamic: false },
|
||||
},
|
||||
wgpu::BindGroupLayoutBinding {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStage::COMPUTE,
|
||||
ty: wgpu::BindingType::StorageBuffer { dynamic: false, readonly: false },
|
||||
},
|
||||
wgpu::BindGroupLayoutBinding {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStage::COMPUTE,
|
||||
ty: wgpu::BindingType::StorageBuffer { dynamic: false, readonly: false },
|
||||
},
|
||||
],
|
||||
});
|
||||
let compute_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
bind_group_layouts: &[&compute_bind_group_layout],
|
||||
});
|
||||
|
||||
|
||||
// create render pipeline with empty bind group layout
|
||||
|
||||
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
bind_group_layouts: &[],
|
||||
});
|
||||
|
||||
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
layout: &render_pipeline_layout,
|
||||
vertex_stage: wgpu::ProgrammableStageDescriptor {
|
||||
module: &vs_module,
|
||||
entry_point: "main",
|
||||
},
|
||||
fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
|
||||
module: &fs_module,
|
||||
entry_point: "main",
|
||||
}),
|
||||
rasterization_state: Some(wgpu::RasterizationStateDescriptor {
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: wgpu::CullMode::None,
|
||||
depth_bias: 0,
|
||||
depth_bias_slope_scale: 0.0,
|
||||
depth_bias_clamp: 0.0,
|
||||
}),
|
||||
primitive_topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
color_states: &[wgpu::ColorStateDescriptor {
|
||||
format: sc_desc.format,
|
||||
color_blend: wgpu::BlendDescriptor::REPLACE,
|
||||
alpha_blend: wgpu::BlendDescriptor::REPLACE,
|
||||
write_mask: wgpu::ColorWrite::ALL,
|
||||
}],
|
||||
depth_stencil_state: None,
|
||||
index_format: wgpu::IndexFormat::Uint16,
|
||||
vertex_buffers: &[
|
||||
wgpu::VertexBufferDescriptor {
|
||||
stride: 4 * 4,
|
||||
step_mode: wgpu::InputStepMode::Instance,
|
||||
attributes: &[
|
||||
// instance position
|
||||
wgpu::VertexAttributeDescriptor {
|
||||
offset: 0,
|
||||
format: wgpu::VertexFormat::Float2,
|
||||
shader_location: 0,
|
||||
},
|
||||
// instance velocity
|
||||
wgpu::VertexAttributeDescriptor {
|
||||
offset: 2 * 4,
|
||||
format: wgpu::VertexFormat::Float2,
|
||||
shader_location: 1,
|
||||
},
|
||||
]
|
||||
},
|
||||
wgpu::VertexBufferDescriptor {
|
||||
stride: 2 * 4,
|
||||
step_mode: wgpu::InputStepMode::Vertex,
|
||||
attributes: &[
|
||||
// vertex positions
|
||||
wgpu::VertexAttributeDescriptor {
|
||||
offset: 0,
|
||||
format: wgpu::VertexFormat::Float2,
|
||||
shader_location: 2,
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
sample_count: 1,
|
||||
sample_mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
});
|
||||
|
||||
|
||||
// create compute pipeline
|
||||
|
||||
let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
|
||||
layout: &compute_pipeline_layout,
|
||||
compute_stage: wgpu::ProgrammableStageDescriptor {
|
||||
module: &boids_module,
|
||||
entry_point: "main",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
// buffer for the three 2d triangle vertices of each instance
|
||||
|
||||
let vertex_buffer_data = [-0.01f32, -0.02, 0.01, -0.02, 0.00, 0.02];
|
||||
let vertices_buffer = device.create_buffer_with_data(vertex_buffer_data.as_bytes(),
|
||||
wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST);
|
||||
|
||||
|
||||
// buffer for simulation parameters uniform
|
||||
|
||||
let sim_param_data = [
|
||||
0.04f32, // deltaT
|
||||
0.1, // rule1Distance
|
||||
0.025, // rule2Distance
|
||||
0.025, // rule3Distance
|
||||
0.02, // rule1Scale
|
||||
0.05, // rule2Scale
|
||||
0.005 // rule3Scale
|
||||
].to_vec();
|
||||
let sim_param_buffer = device.create_buffer_with_data(sim_param_data.as_bytes(),
|
||||
wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST);
|
||||
|
||||
|
||||
// buffer for all particles data of type [(posx,posy,velx,vely),...]
|
||||
|
||||
let mut initial_particle_data = vec![0.0f32; (4 * NUM_PARTICLES) as usize];
|
||||
for particle_instance_chunk in initial_particle_data.chunks_mut(4) {
|
||||
particle_instance_chunk[0] = 2.0 * (rand::random::<f32>() - 0.5); // posx
|
||||
particle_instance_chunk[1] = 2.0 * (rand::random::<f32>() - 0.5); // posy
|
||||
particle_instance_chunk[2] = 2.0 * (rand::random::<f32>() - 0.5) * 0.1; // velx
|
||||
particle_instance_chunk[3] = 2.0 * (rand::random::<f32>() - 0.5) * 0.1; // vely
|
||||
}
|
||||
|
||||
|
||||
// creates two buffers of particle data each of size NUM_PARTICLES
|
||||
// the two buffers alternate as dst and src for each frame
|
||||
|
||||
let mut particle_buffers = Vec::<wgpu::Buffer>::new();
|
||||
let mut particle_bind_groups = Vec::<wgpu::BindGroup>::new();
|
||||
for _i in 0..2 {
|
||||
particle_buffers.push(
|
||||
device.create_buffer_with_data(initial_particle_data.as_bytes(), wgpu::BufferUsage::VERTEX
|
||||
| wgpu::BufferUsage::STORAGE
|
||||
| wgpu::BufferUsage::COPY_DST)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// create two bind groups, one for each buffer as the src
|
||||
// where the alternate buffer is used as the dst
|
||||
|
||||
for i in 0..2 {
|
||||
particle_bind_groups.push(
|
||||
device.create_bind_group(
|
||||
&wgpu::BindGroupDescriptor {
|
||||
layout: &compute_bind_group_layout,
|
||||
bindings: &[
|
||||
wgpu::Binding {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer {
|
||||
buffer: &sim_param_buffer,
|
||||
range: 0 .. (4 * sim_param_data.len() as u64), // 4 = size_of f32
|
||||
},
|
||||
},
|
||||
wgpu::Binding {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Buffer {
|
||||
buffer: &particle_buffers[i],
|
||||
range: 0 .. (4 * initial_particle_data.len() as u64), // 4 = size_of f32
|
||||
},
|
||||
},
|
||||
wgpu::Binding {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::Buffer {
|
||||
buffer: &particle_buffers[(i + 1) % 2], // bind to opposite buffer
|
||||
range: 0 .. (4 * initial_particle_data.len() as u64), // 4 = size_of f32
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// calculates number of work groups from PARTICLES_PER_GROUP constant
|
||||
let work_group_count = ((NUM_PARTICLES as f32) / (PARTICLES_PER_GROUP as f32)).ceil() as u32;
|
||||
|
||||
|
||||
// returns Example struct and No encoder commands
|
||||
|
||||
(Example {
|
||||
particle_bind_groups,
|
||||
particle_buffers,
|
||||
vertices_buffer,
|
||||
compute_pipeline,
|
||||
render_pipeline,
|
||||
work_group_count,
|
||||
frame_num: 0,
|
||||
}, None)
|
||||
}
|
||||
|
||||
/// update is called for any WindowEvent not handled by the framework
|
||||
fn update(&mut self, _event: winit::event::WindowEvent) {
|
||||
//empty
|
||||
}
|
||||
|
||||
/// resize is called on WindowEvent::Resized events
|
||||
fn resize(
|
||||
&mut self,
|
||||
_sc_desc: &wgpu::SwapChainDescriptor,
|
||||
_device: &wgpu::Device,
|
||||
) -> Option<wgpu::CommandBuffer> {
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
/// render is called each frame, dispatching compute groups proportional
|
||||
/// a TriangleList draw call for all NUM_PARTICLES at 3 vertices each
|
||||
fn render(
|
||||
&mut self,
|
||||
frame: &wgpu::SwapChainOutput,
|
||||
device: &wgpu::Device,
|
||||
) -> wgpu::CommandBuffer {
|
||||
|
||||
// create render pass descriptor
|
||||
let render_pass_descriptor = wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
|
||||
attachment: &frame.view,
|
||||
resolve_target: None,
|
||||
load_op: wgpu::LoadOp::Clear,
|
||||
store_op: wgpu::StoreOp::Store,
|
||||
clear_color: wgpu::Color::BLACK,
|
||||
}],
|
||||
depth_stencil_attachment: None,
|
||||
};
|
||||
|
||||
// get command encoder
|
||||
let mut command_encoder =
|
||||
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 });
|
||||
|
||||
{
|
||||
// compute pass
|
||||
let mut cpass = command_encoder.begin_compute_pass();
|
||||
cpass.set_pipeline(&self.compute_pipeline);
|
||||
cpass.set_bind_group(0, &self.particle_bind_groups[self.frame_num % 2], &[]);
|
||||
cpass.dispatch(self.work_group_count, 1, 1);
|
||||
}
|
||||
|
||||
{
|
||||
// render pass
|
||||
let mut rpass = command_encoder.begin_render_pass(&render_pass_descriptor);
|
||||
rpass.set_pipeline(&self.render_pipeline);
|
||||
rpass.set_vertex_buffers(0, &[
|
||||
(&self.particle_buffers[(self.frame_num + 1) % 2], 0), // render dst particles
|
||||
(&self.vertices_buffer, 0), // the three instance-local vertices
|
||||
]);
|
||||
rpass.draw(0..3, 0..NUM_PARTICLES);
|
||||
}
|
||||
|
||||
// update frame count
|
||||
self.frame_num += 1;
|
||||
|
||||
// done
|
||||
command_encoder.finish()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// run example
|
||||
fn main() {
|
||||
framework::run::<Example>("boids");
|
||||
}
|
||||
5
wgpu/examples/boids/shader.frag
Normal file
5
wgpu/examples/boids/shader.frag
Normal file
@@ -0,0 +1,5 @@
|
||||
#version 450
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
void main() {
|
||||
fragColor = vec4(1.0);
|
||||
}
|
||||
11
wgpu/examples/boids/shader.vert
Normal file
11
wgpu/examples/boids/shader.vert
Normal file
@@ -0,0 +1,11 @@
|
||||
#version 450
|
||||
layout(location = 0) in vec2 a_particlePos;
|
||||
layout(location = 1) in vec2 a_particleVel;
|
||||
layout(location = 2) in vec2 a_pos;
|
||||
|
||||
void main() {
|
||||
float angle = -atan(a_particleVel.x, a_particleVel.y);
|
||||
vec2 pos = vec2(a_pos.x * cos(angle) - a_pos.y * sin(angle),
|
||||
a_pos.x * sin(angle) + a_pos.y * cos(angle));
|
||||
gl_Position = vec4(pos + a_particlePos, 0, 1);
|
||||
}
|
||||
Reference in New Issue
Block a user