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