renderling/texture/
mips.rs

1//! Mip-map generation.
2
3use crate::texture::Texture;
4use craballoc::runtime::WgpuRuntime;
5use snafu::Snafu;
6
7use super::wgpu_texture_format_channels_and_subpixel_bytes_todo;
8
9const LABEL: Option<&str> = Some("mip-map-generator");
10
11#[derive(Debug, Snafu)]
12pub enum MipMapError {
13    #[snafu(display("Texture format does not match, expected '{expected:?}' but saw '{seen:?}'"))]
14    TextureMismatch {
15        expected: wgpu::TextureFormat,
16        seen: wgpu::TextureFormat,
17    },
18}
19
20fn create_pipeline(
21    device: &wgpu::Device,
22    format: wgpu::TextureFormat,
23    pp_layout: &wgpu::PipelineLayout,
24) -> wgpu::RenderPipeline {
25    let vertex_linkage = crate::linkage::generate_mipmap_vertex::linkage(device);
26    let fragment_linkage = crate::linkage::generate_mipmap_fragment::linkage(device);
27    device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
28        label: LABEL,
29        layout: Some(pp_layout),
30        vertex: wgpu::VertexState {
31            module: &vertex_linkage.module,
32            entry_point: Some(vertex_linkage.entry_point),
33            buffers: &[],
34            compilation_options: Default::default(),
35        },
36        primitive: wgpu::PrimitiveState {
37            topology: wgpu::PrimitiveTopology::TriangleList,
38            front_face: wgpu::FrontFace::Cw,
39            polygon_mode: wgpu::PolygonMode::Fill,
40            ..Default::default()
41        },
42        fragment: Some(wgpu::FragmentState {
43            module: &fragment_linkage.module,
44            entry_point: Some(fragment_linkage.entry_point),
45            targets: &[Some(wgpu::ColorTargetState {
46                format,
47                blend: None,
48                write_mask: wgpu::ColorWrites::all(),
49            })],
50            compilation_options: Default::default(),
51        }),
52        depth_stencil: None,
53        multisample: wgpu::MultisampleState::default(),
54        multiview: None,
55        cache: None,
56    })
57}
58
59pub struct MipMapGenerator {
60    format: wgpu::TextureFormat,
61    pipeline: wgpu::RenderPipeline,
62    bindgroup_layout: wgpu::BindGroupLayout,
63}
64
65impl MipMapGenerator {
66    pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
67        let bg_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
68            label: LABEL,
69            entries: &[
70                wgpu::BindGroupLayoutEntry {
71                    binding: 0,
72                    visibility: wgpu::ShaderStages::FRAGMENT,
73                    ty: wgpu::BindingType::Texture {
74                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
75                        view_dimension: wgpu::TextureViewDimension::D2,
76                        multisampled: false,
77                    },
78                    count: None,
79                },
80                wgpu::BindGroupLayoutEntry {
81                    binding: 1,
82                    visibility: wgpu::ShaderStages::FRAGMENT,
83                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
84                    count: None,
85                },
86            ],
87        });
88        let pp_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
89            label: LABEL,
90            bind_group_layouts: &[&bg_layout],
91            push_constant_ranges: &[],
92        });
93        let pipeline = create_pipeline(device, format, &pp_layout);
94        Self {
95            format,
96            pipeline,
97            bindgroup_layout: bg_layout,
98        }
99    }
100
101    /// Generate mip maps.
102    ///
103    /// # Errs
104    /// Errors if the texture's format doesn't match the generator format.
105    pub fn generate(
106        &self,
107        runtime: impl AsRef<WgpuRuntime>,
108        texture: &Texture,
109        mip_levels: u32,
110    ) -> Result<Vec<Texture>, MipMapError> {
111        snafu::ensure!(
112            texture.texture.format() == self.format,
113            TextureMismatchSnafu {
114                expected: self.format,
115                seen: texture.texture.format()
116            }
117        );
118
119        let mip_levels = 1.max(mip_levels);
120        let (color_channels, subpixel_bytes) =
121            wgpu_texture_format_channels_and_subpixel_bytes_todo(self.format);
122
123        let size = texture.texture.size();
124        let mut mips: Vec<Texture> = vec![];
125
126        for mip_level in 1..mip_levels {
127            let mip_width = size.width >> mip_level;
128            let mip_height = size.height >> mip_level;
129            let mip_texture = Texture::new_with(
130                runtime.as_ref(),
131                Some(&format!("mip{mip_level}")),
132                Some(
133                    wgpu::TextureUsages::COPY_SRC
134                        | wgpu::TextureUsages::RENDER_ATTACHMENT
135                        | wgpu::TextureUsages::TEXTURE_BINDING,
136                ),
137                None,
138                self.format,
139                color_channels,
140                subpixel_bytes,
141                mip_width,
142                mip_height,
143                1,
144                &[],
145            );
146            let prev_texture = if mip_level == 1 {
147                texture
148            } else {
149                &mips[(mip_level - 2) as usize]
150            };
151            let bindgroup = runtime
152                .as_ref()
153                .device
154                .create_bind_group(&wgpu::BindGroupDescriptor {
155                    label: LABEL,
156                    layout: &self.bindgroup_layout,
157                    entries: &[
158                        wgpu::BindGroupEntry {
159                            binding: 0,
160                            resource: wgpu::BindingResource::TextureView(&prev_texture.view),
161                        },
162                        wgpu::BindGroupEntry {
163                            binding: 1,
164                            resource: wgpu::BindingResource::Sampler(&prev_texture.sampler),
165                        },
166                    ],
167                });
168
169            let mut encoder = runtime
170                .as_ref()
171                .device
172                .create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
173
174            {
175                let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
176                    label: Some(&format!("mip{mip_level}")),
177                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
178                        view: &mip_texture.view,
179                        resolve_target: None,
180                        ops: wgpu::Operations {
181                            load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
182                            store: wgpu::StoreOp::Store,
183                        },
184                        depth_slice: None,
185                    })],
186                    depth_stencil_attachment: None,
187                    ..Default::default()
188                });
189
190                render_pass.set_pipeline(&self.pipeline);
191                render_pass.set_bind_group(0, Some(&bindgroup), &[]);
192                render_pass.draw(0..6, 0..1);
193            }
194
195            runtime
196                .as_ref()
197                .queue
198                .submit(std::iter::once(encoder.finish()));
199
200            mips.push(mip_texture);
201        }
202        Ok(mips)
203    }
204}