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_mask: 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::MipmapFilterMode::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 multiview_mask: None,
174 });
175 if let Some(ref query_sets) = query_sets {
176 rpass.write_timestamp(&query_sets.timestamp, timestamp_query_index_base);
177 rpass.begin_pipeline_statistics_query(
178 &query_sets.pipeline_statistics,
179 pipeline_query_index_base,
180 );
181 }
182 rpass.set_pipeline(&pipeline);
183 rpass.set_bind_group(0, &bind_group, &[]);
184 rpass.draw(0..3, 0..1);
185 if let Some(ref query_sets) = query_sets {
186 rpass.write_timestamp(&query_sets.timestamp, timestamp_query_index_base + 1);
187 rpass.end_pipeline_statistics_query();
188 }
189 }
190
191 if let Some(ref query_sets) = query_sets {
192 let timestamp_query_count = MIP_PASS_COUNT * 2;
193 encoder.resolve_query_set(
194 &query_sets.timestamp,
195 0..timestamp_query_count,
196 &query_sets.data_buffer,
197 0,
198 );
199 encoder.resolve_query_set(
200 &query_sets.pipeline_statistics,
201 0..MIP_PASS_COUNT,
202 &query_sets.data_buffer,
203 pipeline_statistics_offset(),
204 );
205 }
206 }
207}
208
209impl crate::framework::Example for Example {
210 fn optional_features() -> wgpu::Features {
211 QUERY_FEATURES
212 }
213
214 fn init(
215 config: &wgpu::SurfaceConfiguration,
216 _adapter: &wgpu::Adapter,
217 device: &wgpu::Device,
218 queue: &wgpu::Queue,
219 ) -> Self {
220 let mut init_encoder =
221 device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
222
223 let size = 1 << MIP_PASS_COUNT;
225 let texels = create_texels(size as usize, -0.8, 0.156);
226 let texture_extent = wgpu::Extent3d {
227 width: size,
228 height: size,
229 depth_or_array_layers: 1,
230 };
231 let texture = device.create_texture(&wgpu::TextureDescriptor {
232 size: texture_extent,
233 mip_level_count: MIP_LEVEL_COUNT,
234 sample_count: 1,
235 dimension: wgpu::TextureDimension::D2,
236 format: TEXTURE_FORMAT,
237 usage: wgpu::TextureUsages::TEXTURE_BINDING
238 | wgpu::TextureUsages::RENDER_ATTACHMENT
239 | wgpu::TextureUsages::COPY_DST,
240 label: None,
241 view_formats: &[],
242 });
243 let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
244 let temp_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
247 label: Some("Temporary Buffer"),
248 contents: texels.as_slice(),
249 usage: wgpu::BufferUsages::COPY_SRC,
250 });
251 init_encoder.copy_buffer_to_texture(
252 wgpu::TexelCopyBufferInfo {
253 buffer: &temp_buf,
254 layout: wgpu::TexelCopyBufferLayout {
255 offset: 0,
256 bytes_per_row: Some(4 * size),
257 rows_per_image: None,
258 },
259 },
260 texture.as_image_copy(),
261 texture_extent,
262 );
263
264 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
266 label: None,
267 address_mode_u: wgpu::AddressMode::Repeat,
268 address_mode_v: wgpu::AddressMode::Repeat,
269 address_mode_w: wgpu::AddressMode::Repeat,
270 mag_filter: wgpu::FilterMode::Linear,
271 min_filter: wgpu::FilterMode::Linear,
272 mipmap_filter: wgpu::MipmapFilterMode::Linear,
273 ..Default::default()
274 });
275 let mx_total = Self::generate_matrix(config.width as f32 / config.height as f32);
276 let mx_ref: &[f32; 16] = mx_total.as_ref();
277 let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
278 label: Some("Uniform Buffer"),
279 contents: bytemuck::cast_slice(mx_ref),
280 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
281 });
282
283 let shader = device.create_shader_module(wgpu::include_wgsl!("draw.wgsl"));
285
286 let draw_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
287 label: Some("draw"),
288 layout: None,
289 vertex: wgpu::VertexState {
290 module: &shader,
291 entry_point: Some("vs_main"),
292 compilation_options: Default::default(),
293 buffers: &[],
294 },
295 fragment: Some(wgpu::FragmentState {
296 module: &shader,
297 entry_point: Some("fs_main"),
298 compilation_options: Default::default(),
299 targets: &[Some(config.view_formats[0].into())],
300 }),
301 primitive: wgpu::PrimitiveState {
302 topology: wgpu::PrimitiveTopology::TriangleStrip,
303 front_face: wgpu::FrontFace::Ccw,
304 cull_mode: Some(wgpu::Face::Back),
305 ..Default::default()
306 },
307 depth_stencil: None,
308 multisample: wgpu::MultisampleState::default(),
309 multiview_mask: None,
310 cache: None,
311 });
312
313 let bind_group_layout = draw_pipeline.get_bind_group_layout(0);
315 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
316 layout: &bind_group_layout,
317 entries: &[
318 wgpu::BindGroupEntry {
319 binding: 0,
320 resource: uniform_buf.as_entire_binding(),
321 },
322 wgpu::BindGroupEntry {
323 binding: 1,
324 resource: wgpu::BindingResource::TextureView(&texture_view),
325 },
326 wgpu::BindGroupEntry {
327 binding: 2,
328 resource: wgpu::BindingResource::Sampler(&sampler),
329 },
330 ],
331 label: None,
332 });
333
334 let query_sets = if device.features().contains(QUERY_FEATURES) {
336 let mip_passes = MIP_LEVEL_COUNT - 1;
338
339 let timestamp = device.create_query_set(&wgpu::QuerySetDescriptor {
342 label: None,
343 count: mip_passes * 2,
344 ty: wgpu::QueryType::Timestamp,
345 });
346 let timestamp_period = queue.get_timestamp_period();
349
350 let pipeline_statistics = device.create_query_set(&wgpu::QuerySetDescriptor {
352 label: None,
353 count: mip_passes,
354 ty: wgpu::QueryType::PipelineStatistics(
355 wgpu::PipelineStatisticsTypes::FRAGMENT_SHADER_INVOCATIONS,
356 ),
357 });
358
359 let buffer_size = pipeline_statistics_offset()
362 + size_of::<PipelineStatisticsQueries>() as wgpu::BufferAddress;
363 let data_buffer = device.create_buffer(&wgpu::BufferDescriptor {
364 label: Some("query buffer"),
365 size: buffer_size,
366 usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
367 mapped_at_creation: false,
368 });
369
370 let mapping_buffer = device.create_buffer(&wgpu::BufferDescriptor {
372 label: Some("query buffer"),
373 size: buffer_size,
374 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
375 mapped_at_creation: false,
376 });
377
378 Some(QuerySets {
379 timestamp,
380 timestamp_period,
381 pipeline_statistics,
382 data_buffer,
383 mapping_buffer,
384 })
385 } else {
386 None
387 };
388
389 Self::generate_mipmaps(
390 &mut init_encoder,
391 device,
392 &texture,
393 &query_sets,
394 MIP_LEVEL_COUNT,
395 );
396
397 if let Some(ref query_sets) = query_sets {
398 init_encoder.copy_buffer_to_buffer(
399 &query_sets.data_buffer,
400 0,
401 &query_sets.mapping_buffer,
402 0,
403 query_sets.data_buffer.size(),
404 );
405 }
406
407 queue.submit(Some(init_encoder.finish()));
408 if let Some(ref query_sets) = query_sets {
409 query_sets
411 .mapping_buffer
412 .slice(..)
413 .map_async(wgpu::MapMode::Read, |_| ());
414 device.poll(wgpu::PollType::wait_indefinitely()).unwrap();
416 let timestamp_view = query_sets
418 .mapping_buffer
419 .slice(..size_of::<TimestampQueries>() as wgpu::BufferAddress)
420 .get_mapped_range();
421 let pipeline_stats_view = query_sets
422 .mapping_buffer
423 .slice(pipeline_statistics_offset()..)
424 .get_mapped_range();
425 let timestamp_data: &TimestampQueries = bytemuck::from_bytes(×tamp_view);
427 let pipeline_stats_data: &PipelineStatisticsQueries =
428 bytemuck::from_bytes(&pipeline_stats_view);
429 for (idx, (timestamp, pipeline)) in timestamp_data
431 .iter()
432 .zip(pipeline_stats_data.iter())
433 .enumerate()
434 {
435 let nanoseconds =
437 (timestamp.end - timestamp.start) as f32 * query_sets.timestamp_period;
438 let microseconds = nanoseconds / 1000.0;
440 println!(
442 "Generating mip level {} took {:.3} μs and called the fragment shader {} times",
443 idx + 1,
444 microseconds,
445 pipeline
446 );
447 }
448 }
449
450 Example {
451 bind_group,
452 uniform_buf,
453 draw_pipeline,
454 }
455 }
456
457 fn update(&mut self, _event: winit::event::WindowEvent) {
458 }
460
461 fn resize(
462 &mut self,
463 config: &wgpu::SurfaceConfiguration,
464 _device: &wgpu::Device,
465 queue: &wgpu::Queue,
466 ) {
467 let mx_total = Self::generate_matrix(config.width as f32 / config.height as f32);
468 let mx_ref: &[f32; 16] = mx_total.as_ref();
469 queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(mx_ref));
470 }
471
472 fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) {
473 let mut encoder =
474 device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
475 {
476 let clear_color = wgpu::Color {
477 r: 0.1,
478 g: 0.2,
479 b: 0.3,
480 a: 1.0,
481 };
482 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
483 label: None,
484 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
485 view,
486 depth_slice: None,
487 resolve_target: None,
488 ops: wgpu::Operations {
489 load: wgpu::LoadOp::Clear(clear_color),
490 store: wgpu::StoreOp::Store,
491 },
492 })],
493 depth_stencil_attachment: None,
494 timestamp_writes: None,
495 occlusion_query_set: None,
496 multiview_mask: None,
497 });
498 rpass.set_pipeline(&self.draw_pipeline);
499 rpass.set_bind_group(0, &self.bind_group, &[]);
500 rpass.draw(0..4, 0..1);
501 }
502
503 queue.submit(Some(encoder.finish()));
504 }
505}
506
507pub fn main() {
508 crate::framework::run::<Example>("mipmap");
509}
510
511#[cfg(test)]
512#[wgpu_test::gpu_test]
513pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
514 name: "mipmap",
515 image_path: "/examples/features/src/mipmap/screenshot.png",
516 width: 1024,
517 height: 768,
518 optional_features: wgpu::Features::default(),
519 base_test_parameters: wgpu_test::TestParameters::default(),
520 comparisons: &[wgpu_test::ComparisonType::Mean(0.02)],
521 _phantom: std::marker::PhantomData::<Example>,
522};
523
524#[cfg(test)]
525#[wgpu_test::gpu_test]
526pub static TEST_QUERY: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
527 name: "mipmap-query",
528 image_path: "/examples/features/src/mipmap/screenshot_query.png",
529 width: 1024,
530 height: 768,
531 optional_features: QUERY_FEATURES,
532 base_test_parameters: wgpu_test::TestParameters::default(),
533 comparisons: &[
536 wgpu_test::ComparisonType::Mean(0.03),
537 wgpu_test::ComparisonType::Percentile {
538 percentile: 0.99,
539 threshold: 0.1,
540 },
541 ],
542 _phantom: std::marker::PhantomData::<Example>,
543};