diff --git a/wgpu/examples/bunnymark/icon.png b/wgpu/examples/bunnymark/icon.png new file mode 100644 index 000000000..5e72defd2 Binary files /dev/null and b/wgpu/examples/bunnymark/icon.png differ diff --git a/wgpu/examples/bunnymark/main.rs b/wgpu/examples/bunnymark/main.rs new file mode 100644 index 000000000..e3a773586 --- /dev/null +++ b/wgpu/examples/bunnymark/main.rs @@ -0,0 +1,359 @@ +use bytemuck::{Pod, Zeroable}; +use std::{borrow::Cow, mem}; +use wgpu::util::DeviceExt; + +#[path = "../framework.rs"] +mod framework; + +const MAX_BUNNIES: usize = 1 << 20; +const BUNNY_SIZE: f32 = 0.15 * 256.0; +const GRAVITY: f32 = -9.8 * 100.0; +const MAX_VELOCITY: f32 = 750.0; + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct Globals { + mvp: [[f32; 4]; 4], + size: [f32; 2], + pad: [f32; 2], +} + +#[repr(C, align(256))] +#[derive(Clone, Copy, Zeroable)] +struct Locals { + position: [f32; 2], + velocity: [f32; 2], + color: u32, + _pad: u32, +} + +/// Example struct holds references to wgpu resources and frame persistent data +struct Example { + global_group: wgpu::BindGroup, + local_group: wgpu::BindGroup, + pipeline: wgpu::RenderPipeline, + bunnies: Vec, + local_buffer: wgpu::Buffer, + extent: [u32; 2], +} + +impl framework::Example for Example { + /// constructs initial instance of Example struct + fn init( + sc_desc: &wgpu::SwapChainDescriptor, + _adapter: &wgpu::Adapter, + device: &wgpu::Device, + queue: &wgpu::Queue, + ) -> Self { + let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), + flags: wgpu::ShaderFlags::all(), + }); + + let global_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: wgpu::BufferSize::new(mem::size_of::() as _), + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + filtering: true, + comparison: false, + }, + count: None, + }, + ], + label: None, + }); + let local_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: wgpu::BufferSize::new(mem::size_of::() as _), + }, + count: None, + }], + label: None, + }); + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&global_bind_group_layout, &local_bind_group_layout], + push_constant_ranges: &[], + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[wgpu::ColorTargetState { + format: sc_desc.format, + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + write_mask: wgpu::ColorWrite::default(), + }], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleStrip, + ..wgpu::PrimitiveState::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + }); + + let texture = { + let img_data = include_bytes!("icon.png"); + let decoder = png::Decoder::new(std::io::Cursor::new(img_data)); + let (info, mut reader) = decoder.read_info().unwrap(); + let mut buf = vec![0; info.buffer_size()]; + reader.next_frame(&mut buf).unwrap(); + + let size = wgpu::Extent3d { + width: info.width, + height: info.height, + depth_or_array_layers: 1, + }; + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsage::COPY_DST | wgpu::TextureUsage::SAMPLED, + }); + queue.write_texture( + wgpu::ImageCopyTexture { + texture: &texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + }, + &buf, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: std::num::NonZeroU32::new(info.width * 4), + rows_per_image: None, + }, + size, + ); + texture + }; + + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: None, + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + let globals = Globals { + mvp: cgmath::ortho( + 0.0, + sc_desc.width as f32, + 0.0, + sc_desc.height as f32, + -1.0, + 1.0, + ) + .into(), + size: [BUNNY_SIZE; 2], + pad: [0.0; 2], + }; + let global_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("global"), + contents: bytemuck::bytes_of(&globals), + usage: wgpu::BufferUsage::COPY_DST | wgpu::BufferUsage::UNIFORM, + }); + let local_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("local"), + size: (MAX_BUNNIES as wgpu::BufferAddress) * wgpu::BIND_BUFFER_ALIGNMENT, + usage: wgpu::BufferUsage::COPY_DST | wgpu::BufferUsage::UNIFORM, + mapped_at_creation: false, + }); + + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + let global_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &global_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: global_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(&view), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + label: None, + }); + let local_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &local_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: &local_buffer, + offset: 0, + size: wgpu::BufferSize::new(mem::size_of::() as _), + }), + }], + label: None, + }); + + Example { + pipeline, + global_group, + local_group, + bunnies: Vec::new(), + local_buffer, + extent: [sc_desc.width, sc_desc.height], + } + } + + /// update is called for any WindowEvent not handled by the framework + fn update(&mut self, event: winit::event::WindowEvent) { + if let winit::event::WindowEvent::KeyboardInput { + input: + winit::event::KeyboardInput { + virtual_keycode: Some(winit::event::VirtualKeyCode::Space), + state: winit::event::ElementState::Pressed, + .. + }, + .. + } = event + { + let spawn_count = 64 + self.bunnies.len() / 2; + let color = rand::random::(); + println!( + "Spawning {} bunnies, total at {}", + spawn_count, + self.bunnies.len() + spawn_count + ); + for _ in 0..spawn_count { + let speed = rand::random::() * MAX_VELOCITY - (MAX_VELOCITY * 0.5); + self.bunnies.push(Locals { + position: [0.0, 0.5 * (self.extent[1] as f32)], + velocity: [speed, 0.0], + color, + _pad: 0, + }); + } + } + } + + /// resize is called on WindowEvent::Resized events + fn resize( + &mut self, + _sc_desc: &wgpu::SwapChainDescriptor, + _device: &wgpu::Device, + _queue: &wgpu::Queue, + ) { + //empty + } + + /// 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::SwapChainTexture, + device: &wgpu::Device, + queue: &wgpu::Queue, + _spawner: &framework::Spawner, + ) { + let delta = 0.01; + for bunny in self.bunnies.iter_mut() { + bunny.position[0] += bunny.velocity[0] * delta; + bunny.position[1] += bunny.velocity[1] * delta; + bunny.velocity[1] += GRAVITY * delta; + if (bunny.velocity[0] > 0.0 + && bunny.position[0] + 0.5 * BUNNY_SIZE > self.extent[0] as f32) + || (bunny.velocity[0] < 0.0 && bunny.position[0] - 0.5 * BUNNY_SIZE < 0.0) + { + bunny.velocity[0] *= -1.0; + } + if bunny.velocity[1] < 0.0 && bunny.position[1] < 0.5 * BUNNY_SIZE { + bunny.velocity[1] *= -1.0; + } + } + + queue.write_buffer(&self.local_buffer, 0, unsafe { + std::slice::from_raw_parts( + self.bunnies.as_ptr() as *const u8, + self.bunnies.len() * wgpu::BIND_BUFFER_ALIGNMENT as usize, + ) + }); + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + { + let clear_color = wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + }; + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[wgpu::RenderPassColorAttachment { + view: &frame.view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(clear_color), + store: true, + }, + }], + depth_stencil_attachment: None, + }); + rpass.set_pipeline(&self.pipeline); + rpass.set_bind_group(0, &self.global_group, &[]); + for i in 0..self.bunnies.len() { + let offset = (i as wgpu::DynamicOffset) + * (wgpu::BIND_BUFFER_ALIGNMENT as wgpu::DynamicOffset); + rpass.set_bind_group(1, &self.local_group, &[offset]); + rpass.draw(0..4, 0..1); + } + } + + queue.submit(Some(encoder.finish())); + } +} + +fn main() { + framework::run::("bunnymark"); +} diff --git a/wgpu/examples/bunnymark/shader.wgsl b/wgpu/examples/bunnymark/shader.wgsl new file mode 100644 index 000000000..b8639d1ca --- /dev/null +++ b/wgpu/examples/bunnymark/shader.wgsl @@ -0,0 +1,43 @@ +[[block]] +struct Globals { + mvp: mat4x4; + size: vec2; +}; + +[[block]] +struct Locals { + position: vec2; + velocity: vec2; + color: u32; +}; + +[[group(0), binding(0)]] +var globals: Globals; + +[[group(1), binding(0)]] +var locals: Locals; + +struct VertexOutput { + [[builtin(position)]] position: vec4; + [[location(0)]] tex_coords: vec2; + [[location(1)]] color: vec4; +}; + +[[stage(vertex)]] +fn vs_main([[builtin(vertex_index)]] vi: u32) -> VertexOutput { + let tc = vec2(f32(vi & 1u), 0.5 * f32(vi & 2u)); + let offset = vec2(tc.x * globals.size.x, tc.y * globals.size.y); + let pos = globals.mvp * vec4(locals.position + offset, 0.0, 1.0); + let color = vec4((vec4(locals.color) >> vec4(0u, 8u, 16u, 24u)) & vec4(255u)) / 255.0; + return VertexOutput(pos, tc, color); +} + +[[group(0), binding(1)]] +var texture: texture_2d; +[[group(0), binding(2)]] +var sampler: sampler; + +[[stage(fragment)]] +fn fs_main(in: VertexOutput) -> [[location(0)]] vec4 { + return in.color * textureSampleLevel(texture, sampler, in.tex_coords, 0.0); +}