wgpu_examples/bunnymark/
mod.rs

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        // Top boundary check
49        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
55/// Example struct holds references to wgpu resources and frame persistent data
56struct 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    // We're looking for very small differences, so look in the high percentiles.
448    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};