1use bytemuck::{Pod, Zeroable};
2use std::f32::consts;
3use wgpu::util::DeviceExt;
4
5const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb;
6const MIP_LEVEL_COUNT: u32 = 10;
7const MIP_PASS_COUNT: u32 = MIP_LEVEL_COUNT - 1;
8
9const QUERY_FEATURES: wgpu::Features = {
10 wgpu::Features::TIMESTAMP_QUERY
11 .union(wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES)
12 .union(wgpu::Features::PIPELINE_STATISTICS_QUERY)
13};
14
15fn create_texels(size: usize, cx: f32, cy: f32) -> Vec<u8> {
16 use std::iter;
17
18 (0..size * size)
19 .flat_map(|id| {
20 let mut x = 4.0 * (id % size) as f32 / (size - 1) as f32 - 2.0;
22 let mut y = 2.0 * (id / size) as f32 / (size - 1) as f32 - 1.0;
23 let mut count = 0;
24 while count < 0xFF && x * x + y * y < 4.0 {
25 let old_x = x;
26 x = x * x - y * y + cx;
27 y = 2.0 * old_x * y + cy;
28 count += 1;
29 }
30 iter::once(0xFF - (count * 2) as u8)
31 .chain(iter::once(0xFF - (count * 5) as u8))
32 .chain(iter::once(0xFF - (count * 13) as u8))
33 .chain(iter::once(u8::MAX))
34 })
35 .collect()
36}
37
38struct QuerySets {
39 timestamp: wgpu::QuerySet,
40 timestamp_period: f32,
41 pipeline_statistics: wgpu::QuerySet,
42 data_buffer: wgpu::Buffer,
43 mapping_buffer: wgpu::Buffer,
44}
45
46#[repr(C)]
47#[derive(Clone, Copy, Pod, Zeroable)]
48struct TimestampData {
49 start: u64,
50 end: u64,
51}
52
53type TimestampQueries = [TimestampData; MIP_PASS_COUNT as usize];
54type PipelineStatisticsQueries = [u64; MIP_PASS_COUNT as usize];
55
56fn pipeline_statistics_offset() -> wgpu::BufferAddress {
57 (size_of::<TimestampQueries>() as wgpu::BufferAddress).max(wgpu::QUERY_RESOLVE_BUFFER_ALIGNMENT)
58}
59
60struct Example {
61 bind_group: wgpu::BindGroup,
62 uniform_buf: wgpu::Buffer,
63 draw_pipeline: wgpu::RenderPipeline,
64}
65
66impl Example {
67 fn generate_matrix(aspect_ratio: f32) -> glam::Mat4 {
68 let projection = glam::Mat4::perspective_rh(consts::FRAC_PI_4, aspect_ratio, 1.0, 1000.0);
69 let view = glam::Mat4::look_at_rh(
70 glam::Vec3::new(0f32, 0.0, 10.0),
71 glam::Vec3::new(0f32, 50.0, 0.0),
72 glam::Vec3::Z,
73 );
74 projection * view
75 }
76
77 fn generate_mipmaps(
78 encoder: &mut wgpu::CommandEncoder,
79 device: &wgpu::Device,
80 texture: &wgpu::Texture,
81 query_sets: &Option<QuerySets>,
82 mip_count: u32,
83 ) {
84 let shader = device.create_shader_module(wgpu::include_wgsl!("blit.wgsl"));
85
86 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
87 label: Some("blit"),
88 layout: None,
89 vertex: wgpu::VertexState {
90 module: &shader,
91 entry_point: Some("vs_main"),
92 compilation_options: Default::default(),
93 buffers: &[],
94 },
95 fragment: Some(wgpu::FragmentState {
96 module: &shader,
97 entry_point: Some("fs_main"),
98 compilation_options: Default::default(),
99 targets: &[Some(TEXTURE_FORMAT.into())],
100 }),
101 primitive: wgpu::PrimitiveState {
102 topology: wgpu::PrimitiveTopology::TriangleList,
103 ..Default::default()
104 },
105 depth_stencil: None,
106 multisample: wgpu::MultisampleState::default(),
107 multiview: None,
108 cache: None,
109 });
110
111 let bind_group_layout = pipeline.get_bind_group_layout(0);
112
113 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
114 label: Some("mip"),
115 address_mode_u: wgpu::AddressMode::ClampToEdge,
116 address_mode_v: wgpu::AddressMode::ClampToEdge,
117 address_mode_w: wgpu::AddressMode::ClampToEdge,
118 mag_filter: wgpu::FilterMode::Linear,
119 min_filter: wgpu::FilterMode::Linear,
120 mipmap_filter: wgpu::FilterMode::Nearest,
121 ..Default::default()
122 });
123
124 let views = (0..mip_count)
125 .map(|mip| {
126 texture.create_view(&wgpu::TextureViewDescriptor {
127 label: Some("mip"),
128 format: None,
129 dimension: None,
130 usage: None,
131 aspect: wgpu::TextureAspect::All,
132 base_mip_level: mip,
133 mip_level_count: Some(1),
134 base_array_layer: 0,
135 array_layer_count: None,
136 })
137 })
138 .collect::<Vec<_>>();
139
140 for target_mip in 1..mip_count as usize {
141 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
142 layout: &bind_group_layout,
143 entries: &[
144 wgpu::BindGroupEntry {
145 binding: 0,
146 resource: wgpu::BindingResource::TextureView(&views[target_mip - 1]),
147 },
148 wgpu::BindGroupEntry {
149 binding: 1,
150 resource: wgpu::BindingResource::Sampler(&sampler),
151 },
152 ],
153 label: None,
154 });
155
156 let pipeline_query_index_base = target_mip as u32 - 1;
157 let timestamp_query_index_base = (target_mip as u32 - 1) * 2;
158
159 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
160 label: None,
161 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
162 view: &views[target_mip],
163 depth_slice: None,
164 resolve_target: None,
165 ops: wgpu::Operations {
166 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
167 store: wgpu::StoreOp::Store,
168 },
169 })],
170 depth_stencil_attachment: None,
171 timestamp_writes: None,
172 occlusion_query_set: None,
173 });
174 if let Some(ref query_sets) = query_sets {
175 rpass.write_timestamp(&query_sets.timestamp, timestamp_query_index_base);
176 rpass.begin_pipeline_statistics_query(
177 &query_sets.pipeline_statistics,
178 pipeline_query_index_base,
179 );
180 }
181 rpass.set_pipeline(&pipeline);
182 rpass.set_bind_group(0, &bind_group, &[]);
183 rpass.draw(0..3, 0..1);
184 if let Some(ref query_sets) = query_sets {
185 rpass.write_timestamp(&query_sets.timestamp, timestamp_query_index_base + 1);
186 rpass.end_pipeline_statistics_query();
187 }
188 }
189
190 if let Some(ref query_sets) = query_sets {
191 let timestamp_query_count = MIP_PASS_COUNT * 2;
192 encoder.resolve_query_set(
193 &query_sets.timestamp,
194 0..timestamp_query_count,
195 &query_sets.data_buffer,
196 0,
197 );
198 encoder.resolve_query_set(
199 &query_sets.pipeline_statistics,
200 0..MIP_PASS_COUNT,
201 &query_sets.data_buffer,
202 pipeline_statistics_offset(),
203 );
204 }
205 }
206}
207
208impl crate::framework::Example for Example {
209 fn optional_features() -> wgpu::Features {
210 QUERY_FEATURES
211 }
212
213 fn init(
214 config: &wgpu::SurfaceConfiguration,
215 _adapter: &wgpu::Adapter,
216 device: &wgpu::Device,
217 queue: &wgpu::Queue,
218 ) -> Self {
219 let mut init_encoder =
220 device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
221
222 let size = 1 << MIP_PASS_COUNT;
224 let texels = create_texels(size as usize, -0.8, 0.156);
225 let texture_extent = wgpu::Extent3d {
226 width: size,
227 height: size,
228 depth_or_array_layers: 1,
229 };
230 let texture = device.create_texture(&wgpu::TextureDescriptor {
231 size: texture_extent,
232 mip_level_count: MIP_LEVEL_COUNT,
233 sample_count: 1,
234 dimension: wgpu::TextureDimension::D2,
235 format: TEXTURE_FORMAT,
236 usage: wgpu::TextureUsages::TEXTURE_BINDING
237 | wgpu::TextureUsages::RENDER_ATTACHMENT
238 | wgpu::TextureUsages::COPY_DST,
239 label: None,
240 view_formats: &[],
241 });
242 let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
243 let temp_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
246 label: Some("Temporary Buffer"),
247 contents: texels.as_slice(),
248 usage: wgpu::BufferUsages::COPY_SRC,
249 });
250 init_encoder.copy_buffer_to_texture(
251 wgpu::TexelCopyBufferInfo {
252 buffer: &temp_buf,
253 layout: wgpu::TexelCopyBufferLayout {
254 offset: 0,
255 bytes_per_row: Some(4 * size),
256 rows_per_image: None,
257 },
258 },
259 texture.as_image_copy(),
260 texture_extent,
261 );
262
263 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
265 label: None,
266 address_mode_u: wgpu::AddressMode::Repeat,
267 address_mode_v: wgpu::AddressMode::Repeat,
268 address_mode_w: wgpu::AddressMode::Repeat,
269 mag_filter: wgpu::FilterMode::Linear,
270 min_filter: wgpu::FilterMode::Linear,
271 mipmap_filter: wgpu::FilterMode::Linear,
272 ..Default::default()
273 });
274 let mx_total = Self::generate_matrix(config.width as f32 / config.height as f32);
275 let mx_ref: &[f32; 16] = mx_total.as_ref();
276 let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
277 label: Some("Uniform Buffer"),
278 contents: bytemuck::cast_slice(mx_ref),
279 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
280 });
281
282 let shader = device.create_shader_module(wgpu::include_wgsl!("draw.wgsl"));
284
285 let draw_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
286 label: Some("draw"),
287 layout: None,
288 vertex: wgpu::VertexState {
289 module: &shader,
290 entry_point: Some("vs_main"),
291 compilation_options: Default::default(),
292 buffers: &[],
293 },
294 fragment: Some(wgpu::FragmentState {
295 module: &shader,
296 entry_point: Some("fs_main"),
297 compilation_options: Default::default(),
298 targets: &[Some(config.view_formats[0].into())],
299 }),
300 primitive: wgpu::PrimitiveState {
301 topology: wgpu::PrimitiveTopology::TriangleStrip,
302 front_face: wgpu::FrontFace::Ccw,
303 cull_mode: Some(wgpu::Face::Back),
304 ..Default::default()
305 },
306 depth_stencil: None,
307 multisample: wgpu::MultisampleState::default(),
308 multiview: None,
309 cache: None,
310 });
311
312 let bind_group_layout = draw_pipeline.get_bind_group_layout(0);
314 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
315 layout: &bind_group_layout,
316 entries: &[
317 wgpu::BindGroupEntry {
318 binding: 0,
319 resource: uniform_buf.as_entire_binding(),
320 },
321 wgpu::BindGroupEntry {
322 binding: 1,
323 resource: wgpu::BindingResource::TextureView(&texture_view),
324 },
325 wgpu::BindGroupEntry {
326 binding: 2,
327 resource: wgpu::BindingResource::Sampler(&sampler),
328 },
329 ],
330 label: None,
331 });
332
333 let query_sets = if device.features().contains(QUERY_FEATURES) {
335 let mip_passes = MIP_LEVEL_COUNT - 1;
337
338 let timestamp = device.create_query_set(&wgpu::QuerySetDescriptor {
341 label: None,
342 count: mip_passes * 2,
343 ty: wgpu::QueryType::Timestamp,
344 });
345 let timestamp_period = queue.get_timestamp_period();
348
349 let pipeline_statistics = device.create_query_set(&wgpu::QuerySetDescriptor {
351 label: None,
352 count: mip_passes,
353 ty: wgpu::QueryType::PipelineStatistics(
354 wgpu::PipelineStatisticsTypes::FRAGMENT_SHADER_INVOCATIONS,
355 ),
356 });
357
358 let buffer_size = pipeline_statistics_offset()
361 + size_of::<PipelineStatisticsQueries>() as wgpu::BufferAddress;
362 let data_buffer = device.create_buffer(&wgpu::BufferDescriptor {
363 label: Some("query buffer"),
364 size: buffer_size,
365 usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
366 mapped_at_creation: false,
367 });
368
369 let mapping_buffer = device.create_buffer(&wgpu::BufferDescriptor {
371 label: Some("query buffer"),
372 size: buffer_size,
373 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
374 mapped_at_creation: false,
375 });
376
377 Some(QuerySets {
378 timestamp,
379 timestamp_period,
380 pipeline_statistics,
381 data_buffer,
382 mapping_buffer,
383 })
384 } else {
385 None
386 };
387
388 Self::generate_mipmaps(
389 &mut init_encoder,
390 device,
391 &texture,
392 &query_sets,
393 MIP_LEVEL_COUNT,
394 );
395
396 if let Some(ref query_sets) = query_sets {
397 init_encoder.copy_buffer_to_buffer(
398 &query_sets.data_buffer,
399 0,
400 &query_sets.mapping_buffer,
401 0,
402 query_sets.data_buffer.size(),
403 );
404 }
405
406 queue.submit(Some(init_encoder.finish()));
407 if let Some(ref query_sets) = query_sets {
408 query_sets
410 .mapping_buffer
411 .slice(..)
412 .map_async(wgpu::MapMode::Read, |_| ());
413 device.poll(wgpu::PollType::wait()).unwrap();
415 let timestamp_view = query_sets
417 .mapping_buffer
418 .slice(..size_of::<TimestampQueries>() as wgpu::BufferAddress)
419 .get_mapped_range();
420 let pipeline_stats_view = query_sets
421 .mapping_buffer
422 .slice(pipeline_statistics_offset()..)
423 .get_mapped_range();
424 let timestamp_data: &TimestampQueries = bytemuck::from_bytes(×tamp_view);
426 let pipeline_stats_data: &PipelineStatisticsQueries =
427 bytemuck::from_bytes(&pipeline_stats_view);
428 for (idx, (timestamp, pipeline)) in timestamp_data
430 .iter()
431 .zip(pipeline_stats_data.iter())
432 .enumerate()
433 {
434 let nanoseconds =
436 (timestamp.end - timestamp.start) as f32 * query_sets.timestamp_period;
437 let microseconds = nanoseconds / 1000.0;
439 println!(
441 "Generating mip level {} took {:.3} μs and called the fragment shader {} times",
442 idx + 1,
443 microseconds,
444 pipeline
445 );
446 }
447 }
448
449 Example {
450 bind_group,
451 uniform_buf,
452 draw_pipeline,
453 }
454 }
455
456 fn update(&mut self, _event: winit::event::WindowEvent) {
457 }
459
460 fn resize(
461 &mut self,
462 config: &wgpu::SurfaceConfiguration,
463 _device: &wgpu::Device,
464 queue: &wgpu::Queue,
465 ) {
466 let mx_total = Self::generate_matrix(config.width as f32 / config.height as f32);
467 let mx_ref: &[f32; 16] = mx_total.as_ref();
468 queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(mx_ref));
469 }
470
471 fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) {
472 let mut encoder =
473 device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
474 {
475 let clear_color = wgpu::Color {
476 r: 0.1,
477 g: 0.2,
478 b: 0.3,
479 a: 1.0,
480 };
481 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
482 label: None,
483 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
484 view,
485 depth_slice: None,
486 resolve_target: None,
487 ops: wgpu::Operations {
488 load: wgpu::LoadOp::Clear(clear_color),
489 store: wgpu::StoreOp::Store,
490 },
491 })],
492 depth_stencil_attachment: None,
493 timestamp_writes: None,
494 occlusion_query_set: None,
495 });
496 rpass.set_pipeline(&self.draw_pipeline);
497 rpass.set_bind_group(0, &self.bind_group, &[]);
498 rpass.draw(0..4, 0..1);
499 }
500
501 queue.submit(Some(encoder.finish()));
502 }
503}
504
505pub fn main() {
506 crate::framework::run::<Example>("mipmap");
507}
508
509#[cfg(test)]
510#[wgpu_test::gpu_test]
511pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
512 name: "mipmap",
513 image_path: "/examples/features/src/mipmap/screenshot.png",
514 width: 1024,
515 height: 768,
516 optional_features: wgpu::Features::default(),
517 base_test_parameters: wgpu_test::TestParameters::default(),
518 comparisons: &[wgpu_test::ComparisonType::Mean(0.02)],
519 _phantom: std::marker::PhantomData::<Example>,
520};
521
522#[cfg(test)]
523#[wgpu_test::gpu_test]
524pub static TEST_QUERY: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
525 name: "mipmap-query",
526 image_path: "/examples/features/src/mipmap/screenshot_query.png",
527 width: 1024,
528 height: 768,
529 optional_features: QUERY_FEATURES,
530 base_test_parameters: wgpu_test::TestParameters::default(),
531 comparisons: &[wgpu_test::ComparisonType::Mean(0.025)],
532 _phantom: std::marker::PhantomData::<Example>,
533};