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 multiview_mask: None,
126 });
127 rpass.set_pipeline(&self.pipeline);
128 rpass.set_bind_group(0, &self.global_group, &[]);
129 for i in 0..self.bunnies.len() {
130 let offset =
131 (i as wgpu::DynamicOffset) * (uniform_alignment as wgpu::DynamicOffset);
132 rpass.set_bind_group(1, &self.local_group, &[offset]);
133 rpass.draw(0..4, 0..1);
134 }
135 }
136
137 queue.submit(Some(encoder.finish()));
138 }
139}
140
141impl crate::framework::Example for Example {
142 fn init(
143 config: &wgpu::SurfaceConfiguration,
144 _adapter: &wgpu::Adapter,
145 device: &wgpu::Device,
146 queue: &wgpu::Queue,
147 ) -> Self {
148 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
149 label: None,
150 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
151 "../../../../wgpu-hal/examples/halmark/shader.wgsl"
152 ))),
153 });
154
155 let global_bind_group_layout =
156 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
157 entries: &[
158 wgpu::BindGroupLayoutEntry {
159 binding: 0,
160 visibility: wgpu::ShaderStages::VERTEX,
161 ty: wgpu::BindingType::Buffer {
162 ty: wgpu::BufferBindingType::Uniform,
163 has_dynamic_offset: false,
164 min_binding_size: wgpu::BufferSize::new(size_of::<Globals>() as _),
165 },
166 count: None,
167 },
168 wgpu::BindGroupLayoutEntry {
169 binding: 1,
170 visibility: wgpu::ShaderStages::FRAGMENT,
171 ty: wgpu::BindingType::Texture {
172 sample_type: wgpu::TextureSampleType::Float { filterable: true },
173 view_dimension: wgpu::TextureViewDimension::D2,
174 multisampled: false,
175 },
176 count: None,
177 },
178 wgpu::BindGroupLayoutEntry {
179 binding: 2,
180 visibility: wgpu::ShaderStages::FRAGMENT,
181 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
182 count: None,
183 },
184 ],
185 label: None,
186 });
187 let local_bind_group_layout =
188 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
189 entries: &[wgpu::BindGroupLayoutEntry {
190 binding: 0,
191 visibility: wgpu::ShaderStages::VERTEX,
192 ty: wgpu::BindingType::Buffer {
193 ty: wgpu::BufferBindingType::Uniform,
194 has_dynamic_offset: true,
195 min_binding_size: wgpu::BufferSize::new(size_of::<Bunny>() as _),
196 },
197 count: None,
198 }],
199 label: None,
200 });
201 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
202 label: None,
203 bind_group_layouts: &[&global_bind_group_layout, &local_bind_group_layout],
204 push_constant_ranges: &[],
205 });
206
207 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
208 label: None,
209 layout: Some(&pipeline_layout),
210 vertex: wgpu::VertexState {
211 module: &shader,
212 entry_point: Some("vs_main"),
213 compilation_options: Default::default(),
214 buffers: &[],
215 },
216 fragment: Some(wgpu::FragmentState {
217 module: &shader,
218 entry_point: Some("fs_main"),
219 compilation_options: Default::default(),
220 targets: &[Some(wgpu::ColorTargetState {
221 format: config.view_formats[0],
222 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
223 write_mask: wgpu::ColorWrites::default(),
224 })],
225 }),
226 primitive: wgpu::PrimitiveState {
227 topology: wgpu::PrimitiveTopology::TriangleStrip,
228 strip_index_format: Some(wgpu::IndexFormat::Uint16),
229 ..wgpu::PrimitiveState::default()
230 },
231 depth_stencil: None,
232 multisample: wgpu::MultisampleState::default(),
233 multiview_mask: None,
234 cache: None,
235 });
236
237 let texture = {
238 let img_data = include_bytes!("../../../../logo.png");
239 let decoder = png::Decoder::new(std::io::Cursor::new(img_data));
240 let mut reader = decoder.read_info().unwrap();
241 let buf_len = reader
242 .output_buffer_size()
243 .expect("output buffer would not fit in memory");
244 let mut buf = vec![0; buf_len];
245 let info = reader.next_frame(&mut buf).unwrap();
246
247 let size = wgpu::Extent3d {
248 width: info.width,
249 height: info.height,
250 depth_or_array_layers: 1,
251 };
252 let texture = device.create_texture(&wgpu::TextureDescriptor {
253 label: None,
254 size,
255 mip_level_count: 1,
256 sample_count: 1,
257 dimension: wgpu::TextureDimension::D2,
258 format: wgpu::TextureFormat::Rgba8UnormSrgb,
259 usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
260 view_formats: &[],
261 });
262 queue.write_texture(
263 texture.as_image_copy(),
264 &buf,
265 wgpu::TexelCopyBufferLayout {
266 offset: 0,
267 bytes_per_row: Some(info.width * 4),
268 rows_per_image: None,
269 },
270 size,
271 );
272 texture
273 };
274
275 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
276 label: None,
277 address_mode_u: wgpu::AddressMode::ClampToEdge,
278 address_mode_v: wgpu::AddressMode::ClampToEdge,
279 address_mode_w: wgpu::AddressMode::ClampToEdge,
280 mag_filter: wgpu::FilterMode::Linear,
281 min_filter: wgpu::FilterMode::Nearest,
282 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
283 ..Default::default()
284 });
285
286 let globals = Globals {
287 mvp: glam::Mat4::orthographic_rh(
288 0.0,
289 config.width as f32,
290 0.0,
291 config.height as f32,
292 -1.0,
293 1.0,
294 )
295 .to_cols_array_2d(),
296 size: [BUNNY_SIZE; 2],
297 pad: [0.0; 2],
298 };
299
300 let global_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
301 label: Some("global"),
302 contents: bytemuck::bytes_of(&globals),
303 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
304 });
305 let uniform_alignment =
306 device.limits().min_uniform_buffer_offset_alignment as wgpu::BufferAddress;
307 let local_buffer = device.create_buffer(&wgpu::BufferDescriptor {
308 label: Some("local"),
309 size: (MAX_BUNNIES as wgpu::BufferAddress) * uniform_alignment,
310 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
311 mapped_at_creation: false,
312 });
313
314 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
315 let global_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
316 layout: &global_bind_group_layout,
317 entries: &[
318 wgpu::BindGroupEntry {
319 binding: 0,
320 resource: global_buffer.as_entire_binding(),
321 },
322 wgpu::BindGroupEntry {
323 binding: 1,
324 resource: wgpu::BindingResource::TextureView(&view),
325 },
326 wgpu::BindGroupEntry {
327 binding: 2,
328 resource: wgpu::BindingResource::Sampler(&sampler),
329 },
330 ],
331 label: None,
332 });
333 let local_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
334 layout: &local_bind_group_layout,
335 entries: &[wgpu::BindGroupEntry {
336 binding: 0,
337 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
338 buffer: &local_buffer,
339 offset: 0,
340 size: wgpu::BufferSize::new(size_of::<Bunny>() as _),
341 }),
342 }],
343 label: None,
344 });
345
346 let rng = WyRand::new_seed(42);
347
348 let mut ex = Example {
349 view,
350 sampler,
351 global_bind_group_layout,
352 pipeline,
353 global_group,
354 local_group,
355 bunnies: Vec::new(),
356 local_buffer,
357 extent: [config.width, config.height],
358 rng,
359 };
360
361 ex.spawn_bunnies();
362
363 ex
364 }
365
366 fn update(&mut self, event: winit::event::WindowEvent) {
367 if let winit::event::WindowEvent::KeyboardInput {
368 event:
369 KeyEvent {
370 logical_key: Key::Named(NamedKey::Space),
371 state: ElementState::Pressed,
372 ..
373 },
374 ..
375 } = event
376 {
377 self.spawn_bunnies();
378 }
379 }
380
381 fn resize(
382 &mut self,
383 sc_desc: &wgpu::SurfaceConfiguration,
384 device: &wgpu::Device,
385 _queue: &wgpu::Queue,
386 ) {
387 self.extent = [sc_desc.width, sc_desc.height];
388
389 let globals = Globals {
390 mvp: glam::Mat4::orthographic_rh(
391 0.0,
392 sc_desc.width as f32,
393 0.0,
394 sc_desc.height as f32,
395 -1.0,
396 1.0,
397 )
398 .to_cols_array_2d(),
399 size: [BUNNY_SIZE; 2],
400 pad: [0.0; 2],
401 };
402
403 let global_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
404 label: Some("global"),
405 contents: bytemuck::bytes_of(&globals),
406 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
407 });
408
409 let global_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
410 layout: &self.global_bind_group_layout,
411 entries: &[
412 wgpu::BindGroupEntry {
413 binding: 0,
414 resource: global_buffer.as_entire_binding(),
415 },
416 wgpu::BindGroupEntry {
417 binding: 1,
418 resource: wgpu::BindingResource::TextureView(&self.view),
419 },
420 wgpu::BindGroupEntry {
421 binding: 2,
422 resource: wgpu::BindingResource::Sampler(&self.sampler),
423 },
424 ],
425 label: None,
426 });
427 self.global_group = global_group;
428 }
429
430 fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) {
431 self.render_inner(view, device, queue);
432 }
433}
434
435pub fn main() {
436 crate::framework::run::<Example>("bunnymark");
437}
438
439#[cfg(test)]
440#[wgpu_test::gpu_test]
441pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
442 name: "bunnymark",
443 image_path: "/examples/features/src/bunnymark/screenshot.png",
444 width: 1024,
445 height: 768,
446 optional_features: wgpu::Features::default(),
447 base_test_parameters: wgpu_test::TestParameters::default(),
448 comparisons: &[
450 wgpu_test::ComparisonType::Mean(0.05),
451 wgpu_test::ComparisonType::Percentile {
452 percentile: 0.99,
453 threshold: 0.37,
454 },
455 ],
456 _phantom: std::marker::PhantomData::<Example>,
457};