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 mut buf = vec![0; reader.output_buffer_size()];
241 let info = reader.next_frame(&mut buf).unwrap();
242
243 let size = wgpu::Extent3d {
244 width: info.width,
245 height: info.height,
246 depth_or_array_layers: 1,
247 };
248 let texture = device.create_texture(&wgpu::TextureDescriptor {
249 label: None,
250 size,
251 mip_level_count: 1,
252 sample_count: 1,
253 dimension: wgpu::TextureDimension::D2,
254 format: wgpu::TextureFormat::Rgba8UnormSrgb,
255 usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
256 view_formats: &[],
257 });
258 queue.write_texture(
259 texture.as_image_copy(),
260 &buf,
261 wgpu::TexelCopyBufferLayout {
262 offset: 0,
263 bytes_per_row: Some(info.width * 4),
264 rows_per_image: None,
265 },
266 size,
267 );
268 texture
269 };
270
271 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
272 label: None,
273 address_mode_u: wgpu::AddressMode::ClampToEdge,
274 address_mode_v: wgpu::AddressMode::ClampToEdge,
275 address_mode_w: wgpu::AddressMode::ClampToEdge,
276 mag_filter: wgpu::FilterMode::Linear,
277 min_filter: wgpu::FilterMode::Nearest,
278 mipmap_filter: wgpu::FilterMode::Nearest,
279 ..Default::default()
280 });
281
282 let globals = Globals {
283 mvp: glam::Mat4::orthographic_rh(
284 0.0,
285 config.width as f32,
286 0.0,
287 config.height as f32,
288 -1.0,
289 1.0,
290 )
291 .to_cols_array_2d(),
292 size: [BUNNY_SIZE; 2],
293 pad: [0.0; 2],
294 };
295
296 let global_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
297 label: Some("global"),
298 contents: bytemuck::bytes_of(&globals),
299 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
300 });
301 let uniform_alignment =
302 device.limits().min_uniform_buffer_offset_alignment as wgpu::BufferAddress;
303 let local_buffer = device.create_buffer(&wgpu::BufferDescriptor {
304 label: Some("local"),
305 size: (MAX_BUNNIES as wgpu::BufferAddress) * uniform_alignment,
306 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
307 mapped_at_creation: false,
308 });
309
310 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
311 let global_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
312 layout: &global_bind_group_layout,
313 entries: &[
314 wgpu::BindGroupEntry {
315 binding: 0,
316 resource: global_buffer.as_entire_binding(),
317 },
318 wgpu::BindGroupEntry {
319 binding: 1,
320 resource: wgpu::BindingResource::TextureView(&view),
321 },
322 wgpu::BindGroupEntry {
323 binding: 2,
324 resource: wgpu::BindingResource::Sampler(&sampler),
325 },
326 ],
327 label: None,
328 });
329 let local_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
330 layout: &local_bind_group_layout,
331 entries: &[wgpu::BindGroupEntry {
332 binding: 0,
333 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
334 buffer: &local_buffer,
335 offset: 0,
336 size: wgpu::BufferSize::new(size_of::<Bunny>() as _),
337 }),
338 }],
339 label: None,
340 });
341
342 let rng = WyRand::new_seed(42);
343
344 let mut ex = Example {
345 view,
346 sampler,
347 global_bind_group_layout,
348 pipeline,
349 global_group,
350 local_group,
351 bunnies: Vec::new(),
352 local_buffer,
353 extent: [config.width, config.height],
354 rng,
355 };
356
357 ex.spawn_bunnies();
358
359 ex
360 }
361
362 fn update(&mut self, event: winit::event::WindowEvent) {
363 if let winit::event::WindowEvent::KeyboardInput {
364 event:
365 KeyEvent {
366 logical_key: Key::Named(NamedKey::Space),
367 state: ElementState::Pressed,
368 ..
369 },
370 ..
371 } = event
372 {
373 self.spawn_bunnies();
374 }
375 }
376
377 fn resize(
378 &mut self,
379 sc_desc: &wgpu::SurfaceConfiguration,
380 device: &wgpu::Device,
381 _queue: &wgpu::Queue,
382 ) {
383 self.extent = [sc_desc.width, sc_desc.height];
384
385 let globals = Globals {
386 mvp: glam::Mat4::orthographic_rh(
387 0.0,
388 sc_desc.width as f32,
389 0.0,
390 sc_desc.height as f32,
391 -1.0,
392 1.0,
393 )
394 .to_cols_array_2d(),
395 size: [BUNNY_SIZE; 2],
396 pad: [0.0; 2],
397 };
398
399 let global_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
400 label: Some("global"),
401 contents: bytemuck::bytes_of(&globals),
402 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
403 });
404
405 let global_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
406 layout: &self.global_bind_group_layout,
407 entries: &[
408 wgpu::BindGroupEntry {
409 binding: 0,
410 resource: global_buffer.as_entire_binding(),
411 },
412 wgpu::BindGroupEntry {
413 binding: 1,
414 resource: wgpu::BindingResource::TextureView(&self.view),
415 },
416 wgpu::BindGroupEntry {
417 binding: 2,
418 resource: wgpu::BindingResource::Sampler(&self.sampler),
419 },
420 ],
421 label: None,
422 });
423 self.global_group = global_group;
424 }
425
426 fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) {
427 self.render_inner(view, device, queue);
428 }
429}
430
431pub fn main() {
432 crate::framework::run::<Example>("bunnymark");
433}
434
435#[cfg(test)]
436#[wgpu_test::gpu_test]
437pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
438 name: "bunnymark",
439 image_path: "/examples/features/src/bunnymark/screenshot.png",
440 width: 1024,
441 height: 768,
442 optional_features: wgpu::Features::default(),
443 base_test_parameters: wgpu_test::TestParameters::default(),
444 comparisons: &[
446 wgpu_test::ComparisonType::Mean(0.05),
447 wgpu_test::ComparisonType::Percentile {
448 percentile: 0.99,
449 threshold: 0.37,
450 },
451 ],
452 _phantom: std::marker::PhantomData::<Example>,
453};