1use bytemuck::{Pod, Zeroable};
2use nanorand::{Rng, WyRand};
3use std::borrow::Cow;
4use wgpu::util::DeviceExt;
5use winit::{
6 event::{ElementState, KeyEvent},
7 keyboard::{Key, NamedKey},
8};
9
10const MAX_BUNNIES: usize = 1 << 20;
11const BUNNY_SIZE: f32 = 0.15 * 256.0;
12const GRAVITY: f32 = -9.8 * 100.0;
13const MAX_VELOCITY: f32 = 750.0;
14
15#[repr(C)]
16#[derive(Clone, Copy, Pod, Zeroable)]
17struct Globals {
18 mvp: [[f32; 4]; 4],
19 size: [f32; 2],
20 pad: [f32; 2],
21}
22
23#[repr(C, align(256))]
24#[derive(Clone, Copy, Pod, Zeroable)]
25struct Bunny {
26 position: [f32; 2],
27 velocity: [f32; 2],
28 color: u32,
29 _pad: [u32; (256 - 20) / 4],
30}
31
32impl Bunny {
33 fn update_data(&mut self, delta: f32, extent: &[u32; 2]) {
34 self.position[0] += self.velocity[0] * delta;
35 self.position[1] += self.velocity[1] * delta;
36 self.velocity[1] += GRAVITY * delta;
37
38 if (self.velocity[0] > 0.0 && self.position[0] + 0.5 * BUNNY_SIZE > extent[0] as f32)
39 || (self.velocity[0] < 0.0 && self.position[0] - 0.5 * BUNNY_SIZE < 0.0)
40 {
41 self.velocity[0] *= -1.0;
42 }
43
44 if self.velocity[1] < 0.0 && self.position[1] < 0.5 * BUNNY_SIZE {
45 self.velocity[1] *= -1.0;
46 }
47
48 if self.velocity[1] > 0.0 && self.position[1] + 0.5 * BUNNY_SIZE > extent[1] as f32 {
50 self.velocity[1] *= -1.0;
51 }
52 }
53}
54
55struct Example {
57 view: wgpu::TextureView,
58 sampler: wgpu::Sampler,
59 global_bind_group_layout: wgpu::BindGroupLayout,
60 global_group: wgpu::BindGroup,
61 local_group: wgpu::BindGroup,
62 pipeline: wgpu::RenderPipeline,
63 bunnies: Vec<Bunny>,
64 local_buffer: wgpu::Buffer,
65 extent: [u32; 2],
66 rng: WyRand,
67}
68
69impl Example {
70 fn spawn_bunnies(&mut self) {
71 let spawn_count = 64;
72 let color = self.rng.generate::<u32>();
73 println!(
74 "Spawning {} bunnies, total at {}",
75 spawn_count,
76 self.bunnies.len() + spawn_count
77 );
78 for _ in 0..spawn_count {
79 let speed = self.rng.generate::<f32>() * MAX_VELOCITY - (MAX_VELOCITY * 0.5);
80 self.bunnies.push(Bunny {
81 position: [0.0, 0.5 * (self.extent[1] as f32)],
82 velocity: [speed, 0.0],
83 color,
84 _pad: Zeroable::zeroed(),
85 });
86 }
87 }
88
89 fn render_inner(
90 &mut self,
91 view: &wgpu::TextureView,
92 device: &wgpu::Device,
93 queue: &wgpu::Queue,
94 ) {
95 let delta = 0.01;
96 for bunny in self.bunnies.iter_mut() {
97 bunny.update_data(delta, &self.extent);
98 }
99
100 let uniform_alignment = device.limits().min_uniform_buffer_offset_alignment;
101 queue.write_buffer(&self.local_buffer, 0, bytemuck::cast_slice(&self.bunnies));
102
103 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
104 {
105 let clear_color = wgpu::Color {
106 r: 0.1,
107 g: 0.2,
108 b: 0.3,
109 a: 1.0,
110 };
111 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
112 label: None,
113 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
114 view,
115 depth_slice: None,
116 resolve_target: None,
117 ops: wgpu::Operations {
118 load: wgpu::LoadOp::Clear(clear_color),
119 store: wgpu::StoreOp::Store,
120 },
121 })],
122 depth_stencil_attachment: None,
123 timestamp_writes: None,
124 occlusion_query_set: None,
125 });
126 rpass.set_pipeline(&self.pipeline);
127 rpass.set_bind_group(0, &self.global_group, &[]);
128 for i in 0..self.bunnies.len() {
129 let offset =
130 (i as wgpu::DynamicOffset) * (uniform_alignment as wgpu::DynamicOffset);
131 rpass.set_bind_group(1, &self.local_group, &[offset]);
132 rpass.draw(0..4, 0..1);
133 }
134 }
135
136 queue.submit(Some(encoder.finish()));
137 }
138}
139
140impl crate::framework::Example for Example {
141 fn init(
142 config: &wgpu::SurfaceConfiguration,
143 _adapter: &wgpu::Adapter,
144 device: &wgpu::Device,
145 queue: &wgpu::Queue,
146 ) -> Self {
147 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
148 label: None,
149 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
150 "../../../../wgpu-hal/examples/halmark/shader.wgsl"
151 ))),
152 });
153
154 let global_bind_group_layout =
155 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
156 entries: &[
157 wgpu::BindGroupLayoutEntry {
158 binding: 0,
159 visibility: wgpu::ShaderStages::VERTEX,
160 ty: wgpu::BindingType::Buffer {
161 ty: wgpu::BufferBindingType::Uniform,
162 has_dynamic_offset: false,
163 min_binding_size: wgpu::BufferSize::new(size_of::<Globals>() as _),
164 },
165 count: None,
166 },
167 wgpu::BindGroupLayoutEntry {
168 binding: 1,
169 visibility: wgpu::ShaderStages::FRAGMENT,
170 ty: wgpu::BindingType::Texture {
171 sample_type: wgpu::TextureSampleType::Float { filterable: true },
172 view_dimension: wgpu::TextureViewDimension::D2,
173 multisampled: false,
174 },
175 count: None,
176 },
177 wgpu::BindGroupLayoutEntry {
178 binding: 2,
179 visibility: wgpu::ShaderStages::FRAGMENT,
180 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
181 count: None,
182 },
183 ],
184 label: None,
185 });
186 let local_bind_group_layout =
187 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
188 entries: &[wgpu::BindGroupLayoutEntry {
189 binding: 0,
190 visibility: wgpu::ShaderStages::VERTEX,
191 ty: wgpu::BindingType::Buffer {
192 ty: wgpu::BufferBindingType::Uniform,
193 has_dynamic_offset: true,
194 min_binding_size: wgpu::BufferSize::new(size_of::<Bunny>() as _),
195 },
196 count: None,
197 }],
198 label: None,
199 });
200 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
201 label: None,
202 bind_group_layouts: &[&global_bind_group_layout, &local_bind_group_layout],
203 push_constant_ranges: &[],
204 });
205
206 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
207 label: None,
208 layout: Some(&pipeline_layout),
209 vertex: wgpu::VertexState {
210 module: &shader,
211 entry_point: Some("vs_main"),
212 compilation_options: Default::default(),
213 buffers: &[],
214 },
215 fragment: Some(wgpu::FragmentState {
216 module: &shader,
217 entry_point: Some("fs_main"),
218 compilation_options: Default::default(),
219 targets: &[Some(wgpu::ColorTargetState {
220 format: config.view_formats[0],
221 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
222 write_mask: wgpu::ColorWrites::default(),
223 })],
224 }),
225 primitive: wgpu::PrimitiveState {
226 topology: wgpu::PrimitiveTopology::TriangleStrip,
227 strip_index_format: Some(wgpu::IndexFormat::Uint16),
228 ..wgpu::PrimitiveState::default()
229 },
230 depth_stencil: None,
231 multisample: wgpu::MultisampleState::default(),
232 multiview: None,
233 cache: None,
234 });
235
236 let texture = {
237 let img_data = include_bytes!("../../../../logo.png");
238 let decoder = png::Decoder::new(std::io::Cursor::new(img_data));
239 let mut reader = decoder.read_info().unwrap();
240 let buf_len = reader
241 .output_buffer_size()
242 .expect("output buffer would not fit in memory");
243 let mut buf = vec![0; buf_len];
244 let info = reader.next_frame(&mut buf).unwrap();
245
246 let size = wgpu::Extent3d {
247 width: info.width,
248 height: info.height,
249 depth_or_array_layers: 1,
250 };
251 let texture = device.create_texture(&wgpu::TextureDescriptor {
252 label: None,
253 size,
254 mip_level_count: 1,
255 sample_count: 1,
256 dimension: wgpu::TextureDimension::D2,
257 format: wgpu::TextureFormat::Rgba8UnormSrgb,
258 usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
259 view_formats: &[],
260 });
261 queue.write_texture(
262 texture.as_image_copy(),
263 &buf,
264 wgpu::TexelCopyBufferLayout {
265 offset: 0,
266 bytes_per_row: Some(info.width * 4),
267 rows_per_image: None,
268 },
269 size,
270 );
271 texture
272 };
273
274 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
275 label: None,
276 address_mode_u: wgpu::AddressMode::ClampToEdge,
277 address_mode_v: wgpu::AddressMode::ClampToEdge,
278 address_mode_w: wgpu::AddressMode::ClampToEdge,
279 mag_filter: wgpu::FilterMode::Linear,
280 min_filter: wgpu::FilterMode::Nearest,
281 mipmap_filter: wgpu::FilterMode::Nearest,
282 ..Default::default()
283 });
284
285 let globals = Globals {
286 mvp: glam::Mat4::orthographic_rh(
287 0.0,
288 config.width as f32,
289 0.0,
290 config.height as f32,
291 -1.0,
292 1.0,
293 )
294 .to_cols_array_2d(),
295 size: [BUNNY_SIZE; 2],
296 pad: [0.0; 2],
297 };
298
299 let global_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
300 label: Some("global"),
301 contents: bytemuck::bytes_of(&globals),
302 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
303 });
304 let uniform_alignment =
305 device.limits().min_uniform_buffer_offset_alignment as wgpu::BufferAddress;
306 let local_buffer = device.create_buffer(&wgpu::BufferDescriptor {
307 label: Some("local"),
308 size: (MAX_BUNNIES as wgpu::BufferAddress) * uniform_alignment,
309 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
310 mapped_at_creation: false,
311 });
312
313 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
314 let global_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
315 layout: &global_bind_group_layout,
316 entries: &[
317 wgpu::BindGroupEntry {
318 binding: 0,
319 resource: global_buffer.as_entire_binding(),
320 },
321 wgpu::BindGroupEntry {
322 binding: 1,
323 resource: wgpu::BindingResource::TextureView(&view),
324 },
325 wgpu::BindGroupEntry {
326 binding: 2,
327 resource: wgpu::BindingResource::Sampler(&sampler),
328 },
329 ],
330 label: None,
331 });
332 let local_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
333 layout: &local_bind_group_layout,
334 entries: &[wgpu::BindGroupEntry {
335 binding: 0,
336 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
337 buffer: &local_buffer,
338 offset: 0,
339 size: wgpu::BufferSize::new(size_of::<Bunny>() as _),
340 }),
341 }],
342 label: None,
343 });
344
345 let rng = WyRand::new_seed(42);
346
347 let mut ex = Example {
348 view,
349 sampler,
350 global_bind_group_layout,
351 pipeline,
352 global_group,
353 local_group,
354 bunnies: Vec::new(),
355 local_buffer,
356 extent: [config.width, config.height],
357 rng,
358 };
359
360 ex.spawn_bunnies();
361
362 ex
363 }
364
365 fn update(&mut self, event: winit::event::WindowEvent) {
366 if let winit::event::WindowEvent::KeyboardInput {
367 event:
368 KeyEvent {
369 logical_key: Key::Named(NamedKey::Space),
370 state: ElementState::Pressed,
371 ..
372 },
373 ..
374 } = event
375 {
376 self.spawn_bunnies();
377 }
378 }
379
380 fn resize(
381 &mut self,
382 sc_desc: &wgpu::SurfaceConfiguration,
383 device: &wgpu::Device,
384 _queue: &wgpu::Queue,
385 ) {
386 self.extent = [sc_desc.width, sc_desc.height];
387
388 let globals = Globals {
389 mvp: glam::Mat4::orthographic_rh(
390 0.0,
391 sc_desc.width as f32,
392 0.0,
393 sc_desc.height as f32,
394 -1.0,
395 1.0,
396 )
397 .to_cols_array_2d(),
398 size: [BUNNY_SIZE; 2],
399 pad: [0.0; 2],
400 };
401
402 let global_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
403 label: Some("global"),
404 contents: bytemuck::bytes_of(&globals),
405 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
406 });
407
408 let global_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
409 layout: &self.global_bind_group_layout,
410 entries: &[
411 wgpu::BindGroupEntry {
412 binding: 0,
413 resource: global_buffer.as_entire_binding(),
414 },
415 wgpu::BindGroupEntry {
416 binding: 1,
417 resource: wgpu::BindingResource::TextureView(&self.view),
418 },
419 wgpu::BindGroupEntry {
420 binding: 2,
421 resource: wgpu::BindingResource::Sampler(&self.sampler),
422 },
423 ],
424 label: None,
425 });
426 self.global_group = global_group;
427 }
428
429 fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) {
430 self.render_inner(view, device, queue);
431 }
432}
433
434pub fn main() {
435 crate::framework::run::<Example>("bunnymark");
436}
437
438#[cfg(test)]
439#[wgpu_test::gpu_test]
440pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
441 name: "bunnymark",
442 image_path: "/examples/features/src/bunnymark/screenshot.png",
443 width: 1024,
444 height: 768,
445 optional_features: wgpu::Features::default(),
446 base_test_parameters: wgpu_test::TestParameters::default(),
447 comparisons: &[
449 wgpu_test::ComparisonType::Mean(0.05),
450 wgpu_test::ComparisonType::Percentile {
451 percentile: 0.99,
452 threshold: 0.37,
453 },
454 ],
455 _phantom: std::marker::PhantomData::<Example>,
456};