1use super::compute::*;
2use super::copy::*;
3use super::render::*;
4use crate::core::*;
5use log::{debug, warn};
6
7#[derive(Debug)]
9pub struct RecommendedMipmapGenerator {
10 render: RenderMipmapGenerator,
11 compute: ComputeMipmapGenerator,
12}
13
14const SUPPORTED_FORMATS: [wgpu::TextureFormat; 17] = {
16 use wgpu::TextureFormat;
17 [
18 TextureFormat::R8Unorm,
19 TextureFormat::R8Snorm,
20 TextureFormat::R16Float,
21 TextureFormat::Rg8Unorm,
22 TextureFormat::Rg8Snorm,
23 TextureFormat::R32Float,
24 TextureFormat::Rg16Float,
25 TextureFormat::Rgba8Unorm,
26 TextureFormat::Rgba8Snorm,
27 TextureFormat::Bgra8Unorm,
28 TextureFormat::Bgra8UnormSrgb,
29 TextureFormat::Rgba8UnormSrgb,
30 TextureFormat::Rgb10a2Unorm,
31 TextureFormat::Rg11b10Float,
32 TextureFormat::Rg32Float,
33 TextureFormat::Rgba16Float,
34 TextureFormat::Rgba32Float,
35 ]
36};
37
38impl RecommendedMipmapGenerator {
39 pub fn new(device: &wgpu::Device) -> Self {
42 Self::new_with_format_hints(device, &SUPPORTED_FORMATS)
43 }
44
45 pub fn new_with_format_hints(
48 device: &wgpu::Device,
49 format_hints: &[wgpu::TextureFormat],
50 ) -> Self {
51 for format in format_hints {
52 if !SUPPORTED_FORMATS.contains(&format) {
53 warn!("[RecommendedMipmapGenerator::new] No support for requested texture format {:?}", *format);
54 warn!("[RecommendedMipmapGenerator::new] Attempting to continue, but calls to generate may fail or produce unexpected results.");
55 continue;
56 }
57 }
58 let render = RenderMipmapGenerator::new_with_format_hints(device, format_hints);
59 let compute = ComputeMipmapGenerator::new_with_format_hints(device, format_hints);
60 Self { render, compute }
61 }
62}
63
64impl MipmapGenerator for RecommendedMipmapGenerator {
65 fn generate(
66 &self,
67 device: &wgpu::Device,
68 encoder: &mut wgpu::CommandEncoder,
69 texture: &wgpu::Texture,
70 texture_descriptor: &wgpu::TextureDescriptor,
71 ) -> Result<(), Error> {
72 match self
74 .compute
75 .generate(device, encoder, texture, texture_descriptor)
76 {
77 Err(e) => {
78 debug!("[RecommendedMipmapGenerator::generate] compute error {}.\n falling back to render backend.", e);
79 }
80 ok => return ok,
81 };
82 match self
84 .render
85 .generate(device, encoder, texture, texture_descriptor)
86 {
87 Err(e) => {
88 debug!("[RecommendedMipmapGenerator::generate] render error {}.\n falling back to copy backend.", e);
89 }
90 ok => return ok,
91 };
92 match CopyMipmapGenerator::new(&self.render).generate(
94 device,
95 encoder,
96 texture,
97 texture_descriptor,
98 ) {
99 Err(e) => {
100 debug!("[RecommendedMipmapGenerator::generate] copy error {}.", e);
101 }
102 ok => return ok,
103 }
104 Err(Error::UnsupportedUsage(texture_descriptor.usage))
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::util::*;
112
113 fn init() {
114 let _ = env_logger::builder().is_test(true).try_init();
115 }
116
117 #[allow(dead_code)]
118 async fn generate_and_copy_to_cpu_recommended(
119 buffer: &[u8],
120 texture_descriptor: &wgpu::TextureDescriptor<'_>,
121 ) -> Result<Vec<MipBuffer>, Error> {
122 let (_instance, _adaptor, device, queue) = wgpu_setup().await;
123 let generator = crate::backends::RecommendedMipmapGenerator::new_with_format_hints(
124 &device,
125 &[texture_descriptor.format],
126 );
127 Ok(
128 generate_and_copy_to_cpu(&device, &queue, &generator, buffer, texture_descriptor)
129 .await?,
130 )
131 }
132 #[test]
133 fn checkerboard_r8_render() {
134 init();
135 let size = 512;
137 let mip_level_count = 1 + (size as f32).log2() as u32;
138 let checkboard_size = 16;
139 let data = checkerboard_r8(size, size, checkboard_size);
140 let format = wgpu::TextureFormat::R8Unorm;
142 let texture_extent = wgpu::Extent3d {
143 width: size,
144 height: size,
145 depth: 1,
146 };
147 let texture_descriptor = wgpu::TextureDescriptor {
148 size: texture_extent,
149 mip_level_count,
150 format,
151 sample_count: 1,
152 dimension: wgpu::TextureDimension::D2,
153 usage: crate::RenderMipmapGenerator::required_usage()
154 | wgpu::TextureUsage::COPY_DST
155 | wgpu::TextureUsage::COPY_SRC,
156 label: None,
157 };
158 dbg!(format);
159 futures::executor::block_on((|| async {
160 let mipmap_buffers = generate_and_copy_to_cpu_recommended(&data, &texture_descriptor)
161 .await
162 .unwrap();
163
164 assert!(mipmap_buffers.len() == mip_level_count as usize);
165
166 for mip in &mipmap_buffers {
167 assert!(
168 mip.buffer.len()
169 == mip.dimensions.unpadded_bytes_per_row * mip.dimensions.height
170 );
171 }
172 mipmap_buffers.last().map(|mip| {
174 let width = mip.dimensions.width;
175 let height = mip.dimensions.height;
176 let bpp = mip.dimensions.bytes_per_channel;
177 let data = &mip.buffer;
178 dbg!(data);
179 assert!(width == 1);
180 assert!(height == 1);
181 assert!(data.len() == width * height * bpp);
182 assert!(data[0] == 127 || data[0] == 128);
188 });
189 })());
190 }
191
192 #[test]
193 fn checkerboard_rgba8_render() {
194 init();
195 let size = 512;
197 let mip_level_count = 1 + (size as f32).log2() as u32;
198 let checkboard_size = 16;
199 let data = checkerboard_rgba8(size, size, checkboard_size);
200 let format = wgpu::TextureFormat::Rgba8Unorm;
202 let texture_extent = wgpu::Extent3d {
203 width: size,
204 height: size,
205 depth: 1,
206 };
207 let texture_descriptor = wgpu::TextureDescriptor {
208 size: texture_extent,
209 mip_level_count,
210 format,
211 sample_count: 1,
212 dimension: wgpu::TextureDimension::D2,
213 usage: crate::RenderMipmapGenerator::required_usage()
214 | wgpu::TextureUsage::COPY_DST
215 | wgpu::TextureUsage::COPY_SRC,
216 label: None,
217 };
218 futures::executor::block_on((|| async {
219 let mipmap_buffers = generate_and_copy_to_cpu_recommended(&data, &texture_descriptor)
220 .await
221 .unwrap();
222 assert!(mipmap_buffers.len() == mip_level_count as usize);
223 for mip in &mipmap_buffers {
224 assert!(
225 mip.buffer.len()
226 == mip.dimensions.unpadded_bytes_per_row * mip.dimensions.height
227 );
228 }
229 mipmap_buffers.last().map(|mip| {
232 let width = mip.dimensions.width;
233 let height = mip.dimensions.height;
234 let bpp = mip.dimensions.bytes_per_channel;
235 let data = &mip.buffer;
236 assert!(width == 1);
237 assert!(height == 1);
238 assert!(data.len() == width * height * bpp);
239 assert!(data[0] == 127 || data[0] == 128);
240 assert!(data[1] == 127 || data[1] == 128);
241 assert!(data[2] == 127 || data[2] == 128);
242 assert!(data[3] == 255);
243 });
244 })());
245 }
246
247 #[test]
248 fn checkerboard_srgba8_render() {
249 init();
250 let size = 512;
252 let mip_level_count = 1 + (size as f32).log2() as u32;
253 let checkboard_size = 16;
254 let data = checkerboard_rgba8(size, size, checkboard_size);
255 let format = wgpu::TextureFormat::Rgba8UnormSrgb;
257 let texture_extent = wgpu::Extent3d {
258 width: size,
259 height: size,
260 depth: 1,
261 };
262 let texture_descriptor = wgpu::TextureDescriptor {
263 size: texture_extent,
264 mip_level_count,
265 format,
266 sample_count: 1,
267 dimension: wgpu::TextureDimension::D2,
268 usage: crate::RenderMipmapGenerator::required_usage()
269 | wgpu::TextureUsage::COPY_DST
270 | wgpu::TextureUsage::COPY_SRC,
271 label: None,
272 };
273 futures::executor::block_on((|| async {
274 let mipmap_buffers = generate_and_copy_to_cpu_recommended(&data, &texture_descriptor)
275 .await
276 .unwrap();
277 assert!(mipmap_buffers.len() == mip_level_count as usize);
278 for mip in &mipmap_buffers {
279 assert!(
280 mip.buffer.len()
281 == mip.dimensions.unpadded_bytes_per_row * mip.dimensions.height
282 );
283 }
284 mipmap_buffers.last().map(|mip| {
287 let width = mip.dimensions.width;
288 let height = mip.dimensions.height;
289 let bpp = mip.dimensions.bytes_per_channel;
290 let data = &mip.buffer;
291 assert!(width == 1);
292 assert!(height == 1);
293 assert!(data.len() == width * height * bpp);
294 assert!(data[0] == 187 || data[0] == 188);
301 assert!(data[1] == 187 || data[1] == 188);
302 assert!(data[2] == 187 || data[2] == 188);
303 assert!(data[3] == 255);
304 });
305 })());
306 }
307
308 #[test]
309 fn checkerboard_r8_compute() {
310 init();
311 let size = 512;
313 let mip_level_count = 1 + (size as f32).log2() as u32;
314 let checkboard_size = 16;
315 let data = checkerboard_r8(size, size, checkboard_size);
316 let format = wgpu::TextureFormat::R8Unorm;
318 let texture_extent = wgpu::Extent3d {
319 width: size,
320 height: size,
321 depth: 1,
322 };
323 let texture_descriptor = wgpu::TextureDescriptor {
324 size: texture_extent,
325 mip_level_count,
326 format,
327 sample_count: 1,
328 dimension: wgpu::TextureDimension::D2,
329 usage: crate::ComputeMipmapGenerator::required_usage()
330 | wgpu::TextureUsage::COPY_DST
331 | wgpu::TextureUsage::COPY_SRC,
332 label: None,
333 };
334 futures::executor::block_on((|| async {
335 let mipmap_buffers = generate_and_copy_to_cpu_recommended(&data, &texture_descriptor)
336 .await
337 .unwrap();
338
339 assert!(mipmap_buffers.len() == mip_level_count as usize);
340
341 for mip in &mipmap_buffers {
342 assert!(
343 mip.buffer.len()
344 == mip.dimensions.unpadded_bytes_per_row * mip.dimensions.height
345 );
346 }
347 mipmap_buffers.last().map(|mip| {
349 let width = mip.dimensions.width;
350 let height = mip.dimensions.height;
351 let bpp = mip.dimensions.bytes_per_channel;
352 let data = &mip.buffer;
353 assert!(width == 1);
354 assert!(height == 1);
355 assert!(data.len() == width * height * bpp);
356 assert!(data[0] == 127 || data[0] == 128);
362 });
363 })());
364 }
365
366 #[test]
367 fn checkerboard_rgba8_compute() {
368 init();
369 let size = 512;
371 let mip_level_count = 1 + (size as f32).log2() as u32;
372 let checkboard_size = 16;
373 let data = checkerboard_rgba8(size, size, checkboard_size);
374 let format = wgpu::TextureFormat::Rgba8Unorm;
376 let texture_extent = wgpu::Extent3d {
377 width: size,
378 height: size,
379 depth: 1,
380 };
381 let texture_descriptor = wgpu::TextureDescriptor {
382 size: texture_extent,
383 mip_level_count,
384 format,
385 sample_count: 1,
386 dimension: wgpu::TextureDimension::D2,
387 usage: crate::ComputeMipmapGenerator::required_usage()
388 | wgpu::TextureUsage::COPY_DST
389 | wgpu::TextureUsage::COPY_SRC,
390 label: None,
391 };
392 futures::executor::block_on((|| async {
393 let mipmap_buffers = generate_and_copy_to_cpu_recommended(&data, &texture_descriptor)
394 .await
395 .unwrap();
396 assert!(mipmap_buffers.len() == mip_level_count as usize);
397 for mip in &mipmap_buffers {
398 assert!(
399 mip.buffer.len()
400 == mip.dimensions.unpadded_bytes_per_row * mip.dimensions.height
401 );
402 }
403 mipmap_buffers.last().map(|mip| {
406 let width = mip.dimensions.width;
407 let height = mip.dimensions.height;
408 let bpp = mip.dimensions.bytes_per_channel;
409 let data = &mip.buffer;
410 assert!(width == 1);
411 assert!(height == 1);
412 assert!(data.len() == width * height * bpp);
413 assert!(data[0] == 127 || data[0] == 128);
414 assert!(data[1] == 127 || data[1] == 128);
415 assert!(data[2] == 127 || data[2] == 128);
416 assert!(data[3] == 255);
417 });
418 })());
419 }
420
421 #[test]
422 fn checkerboard_srgba8_compute() {
423 init();
424 let size = 512;
426 let mip_level_count = 1 + (size as f32).log2() as u32;
427 let checkboard_size = 16;
428 let data = checkerboard_rgba8(size, size, checkboard_size);
429 let format = wgpu::TextureFormat::Rgba8UnormSrgb;
431 let texture_extent = wgpu::Extent3d {
432 width: size,
433 height: size,
434 depth: 1,
435 };
436 let texture_descriptor = wgpu::TextureDescriptor {
437 size: texture_extent,
438 mip_level_count,
439 format,
440 sample_count: 1,
441 dimension: wgpu::TextureDimension::D2,
442 usage: crate::ComputeMipmapGenerator::required_usage()
443 | wgpu::TextureUsage::COPY_DST
444 | wgpu::TextureUsage::COPY_SRC,
445 label: None,
446 };
447 futures::executor::block_on((|| async {
448 let mipmap_buffers = generate_and_copy_to_cpu_recommended(&data, &texture_descriptor)
449 .await
450 .unwrap();
451 assert!(mipmap_buffers.len() == mip_level_count as usize);
452 for mip in &mipmap_buffers {
453 assert!(
454 mip.buffer.len()
455 == mip.dimensions.unpadded_bytes_per_row * mip.dimensions.height
456 );
457 }
458 mipmap_buffers.last().map(|mip| {
461 let width = mip.dimensions.width;
462 let height = mip.dimensions.height;
463 let bpp = mip.dimensions.bytes_per_channel;
464 let data = &mip.buffer;
465 assert!(width == 1);
466 assert!(height == 1);
467 assert!(data.len() == width * height * bpp);
468 dbg!(data);
475 assert!(data[0] == 187 || data[0] == 188);
476 assert!(data[1] == 187 || data[1] == 188);
477 assert!(data[2] == 187 || data[2] == 188);
478 assert!(data[3] == 255);
479 });
480 })());
481 }
482
483 #[test]
484 fn checkerboard_rgba32f_render() {
485 init();
486 let size = 512;
488 let mip_level_count = 1 + (size as f32).log2() as u32;
489 let checkboard_size = 16;
490 let data = checkerboard_rgba32f(size, size, checkboard_size);
491 let format = wgpu::TextureFormat::Rgba32Float;
493 let texture_extent = wgpu::Extent3d {
494 width: size,
495 height: size,
496 depth: 1,
497 };
498 let texture_descriptor = wgpu::TextureDescriptor {
499 size: texture_extent,
500 mip_level_count,
501 format,
502 sample_count: 1,
503 dimension: wgpu::TextureDimension::D2,
504 usage: crate::RenderMipmapGenerator::required_usage()
505 | wgpu::TextureUsage::COPY_DST
506 | wgpu::TextureUsage::COPY_SRC,
507 label: None,
508 };
509 futures::executor::block_on((|| async {
510 let mipmap_buffers = generate_and_copy_to_cpu_recommended(
511 bytemuck::cast_slice(&data),
512 &texture_descriptor,
513 )
514 .await
515 .unwrap();
516 assert!(mipmap_buffers.len() == mip_level_count as usize);
517 for mip in &mipmap_buffers {
518 assert!(
519 mip.buffer.len()
520 == mip.dimensions.unpadded_bytes_per_row * mip.dimensions.height
521 );
522 }
523 mipmap_buffers.last().map(|mip| {
526 let width = mip.dimensions.width;
527 let height = mip.dimensions.height;
528 let bpp = mip.dimensions.bytes_per_channel;
529 let data = &mip.buffer;
530 assert!(width == 1);
531 assert!(height == 1);
532 assert!(data.len() == width * height * bpp);
533 let data: &[f32] = bytemuck::try_cast_slice(data).unwrap();
534 dbg!(data);
535 assert!(data[0] == 0.5);
536 assert!(data[1] == 0.5);
537 assert!(data[2] == 0.5);
538 assert!(data[3] == 1.0);
539 });
540 })());
541 }
542
543 #[test]
544 fn checkerboard_rgba32f_compute() {
545 init();
546 let size = 512;
548 let mip_level_count = 1 + (size as f32).log2() as u32;
549 let checkboard_size = 16;
550 let data = checkerboard_rgba32f(size, size, checkboard_size);
551 let format = wgpu::TextureFormat::Rgba32Float;
553 let texture_extent = wgpu::Extent3d {
554 width: size,
555 height: size,
556 depth: 1,
557 };
558 let texture_descriptor = wgpu::TextureDescriptor {
559 size: texture_extent,
560 mip_level_count,
561 format,
562 sample_count: 1,
563 dimension: wgpu::TextureDimension::D2,
564 usage: crate::ComputeMipmapGenerator::required_usage()
565 | wgpu::TextureUsage::COPY_DST
566 | wgpu::TextureUsage::COPY_SRC,
567 label: None,
568 };
569 futures::executor::block_on((|| async {
570 let mipmap_buffers = generate_and_copy_to_cpu_recommended(
571 bytemuck::cast_slice(&data),
572 &texture_descriptor,
573 )
574 .await
575 .unwrap();
576 assert!(mipmap_buffers.len() == mip_level_count as usize);
577 for mip in &mipmap_buffers {
578 assert!(
579 mip.buffer.len()
580 == mip.dimensions.unpadded_bytes_per_row * mip.dimensions.height
581 );
582 }
583 mipmap_buffers.last().map(|mip| {
586 let width = mip.dimensions.width;
587 let height = mip.dimensions.height;
588 let bpp = mip.dimensions.bytes_per_channel;
589 let data = &mip.buffer;
590 assert!(width == 1);
591 assert!(height == 1);
592 assert!(data.len() == width * height * bpp);
593 let data: &[f32] = bytemuck::try_cast_slice(data).unwrap();
594 dbg!(data);
595 assert!(data[0] == 0.5);
596 assert!(data[1] == 0.5);
597 assert!(data[2] == 0.5);
598 assert!(data[3] == 1.0);
599 });
600 })());
601 }
602
603 #[test]
604 fn checkerboard_rgba8_copy() {
605 init();
606 let size = 512;
608 let mip_level_count = 1 + (size as f32).log2() as u32;
609 let checkboard_size = 16;
610 let data = checkerboard_rgba8(size, size, checkboard_size);
611 let format = wgpu::TextureFormat::Rgba8Unorm;
613 let texture_extent = wgpu::Extent3d {
614 width: size,
615 height: size,
616 depth: 1,
617 };
618 let texture_descriptor = wgpu::TextureDescriptor {
619 size: texture_extent,
620 mip_level_count,
621 format,
622 sample_count: 1,
623 dimension: wgpu::TextureDimension::D2,
624 usage: crate::CopyMipmapGenerator::required_usage()
625 | wgpu::TextureUsage::COPY_SRC
626 | wgpu::TextureUsage::COPY_DST,
627 label: None,
628 };
629 futures::executor::block_on((|| async {
630 let mipmap_buffers = generate_and_copy_to_cpu_recommended(&data, &texture_descriptor)
631 .await
632 .unwrap();
633 assert!(mipmap_buffers.len() == mip_level_count as usize);
634 for mip in &mipmap_buffers {
635 assert!(
636 mip.buffer.len()
637 == mip.dimensions.unpadded_bytes_per_row * mip.dimensions.height
638 );
639 }
640 mipmap_buffers.last().map(|mip| {
643 let width = mip.dimensions.width;
644 let height = mip.dimensions.height;
645 let bpp = mip.dimensions.bytes_per_channel;
646 let data = &mip.buffer;
647 dbg!(data);
648 assert!(width == 1);
649 assert!(height == 1);
650 assert!(data.len() == width * height * bpp);
651 assert!(data[0] == 127 || data[0] == 128);
652 assert!(data[1] == 127 || data[1] == 128);
653 assert!(data[2] == 127 || data[2] == 128);
654 assert!(data[3] == 255);
655 });
656 })());
657 }
658}