1use crate::utils::get_mip_extent;
5use std::collections::HashMap;
6use thiserror::Error;
7use wgpu::{
8 util::make_spirv, AddressMode, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry,
9 BindingResource, BindingType, CommandEncoder, Device, FilterMode, FragmentState, FrontFace, LoadOp, MultisampleState, Operations,
10 PipelineLayoutDescriptor, PrimitiveState, RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor, Sampler, SamplerDescriptor,
11 ShaderModuleDescriptor, Texture, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages,
12 TextureViewDescriptor, TextureViewDimension, VertexState,
13};
14
15#[derive(Debug)]
17pub struct RenderMipmapGenerator {
18 sampler: Sampler,
19 layout_cache: HashMap<TextureSampleType, BindGroupLayout>,
20 pipeline_cache: HashMap<TextureFormat, RenderPipeline>,
21}
22
23#[allow(clippy::match_same_arms)]
24fn to_sample_type(format: TextureFormat) -> TextureSampleType {
25 match format {
26 TextureFormat::R8Uint
27 | TextureFormat::R16Uint
28 | TextureFormat::Rg8Uint
29 | TextureFormat::R32Uint
30 | TextureFormat::Rg16Uint
31 | TextureFormat::Rgba8Uint
32 | TextureFormat::Rg32Uint
33 | TextureFormat::Rgba16Uint
34 | TextureFormat::Rgba32Uint => TextureSampleType::Uint,
35
36 TextureFormat::R8Sint
37 | TextureFormat::R16Sint
38 | TextureFormat::Rg8Sint
39 | TextureFormat::R32Sint
40 | TextureFormat::Rg16Sint
41 | TextureFormat::Rgba8Sint
42 | TextureFormat::Rg32Sint
43 | TextureFormat::Rgba16Sint
44 | TextureFormat::Rgba32Sint => TextureSampleType::Sint,
45
46 TextureFormat::R8Unorm
47 | TextureFormat::R8Snorm
48 | TextureFormat::R16Float
49 | TextureFormat::Rg8Unorm
50 | TextureFormat::Rg8Snorm
51 | TextureFormat::R32Float
52 | TextureFormat::Rg16Float
53 | TextureFormat::Rgba8Unorm
54 | TextureFormat::Rgba8UnormSrgb
55 | TextureFormat::Rgba8Snorm
56 | TextureFormat::Bgra8Unorm
57 | TextureFormat::Bgra8UnormSrgb
58 | TextureFormat::Rgb10a2Unorm
59 | TextureFormat::Rg11b10Float
60 | TextureFormat::Rg32Float
61 | TextureFormat::Rgba16Float
62 | TextureFormat::Rgba32Float
63 | TextureFormat::Depth32Float
64 | TextureFormat::Depth24Plus
65 | TextureFormat::Depth24PlusStencil8
66 | TextureFormat::Bc1RgbaUnorm
67 | TextureFormat::Bc1RgbaUnormSrgb
68 | TextureFormat::Bc2RgbaUnorm
69 | TextureFormat::Bc2RgbaUnormSrgb
70 | TextureFormat::Bc3RgbaUnorm
71 | TextureFormat::Bc3RgbaUnormSrgb
72 | TextureFormat::Bc4RUnorm
73 | TextureFormat::Bc4RSnorm
74 | TextureFormat::Bc5RgUnorm
75 | TextureFormat::Bc5RgSnorm
76 | TextureFormat::Bc6hRgbUfloat
77 | TextureFormat::Bc7RgbaUnorm
79 | TextureFormat::Bc7RgbaUnormSrgb
80 => TextureSampleType::Float { filterable: true },
119
120 _ => TextureSampleType::Float { filterable: true },
121 }
122}
123
124impl RenderMipmapGenerator {
125 pub fn required_usage() -> TextureUsages {
128 TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING
129 }
130
131 #[allow(clippy::too_many_lines)]
135 pub fn new_with_format_hints(device: &Device, format_hints: &[TextureFormat]) -> Self {
136 let sampler = device.create_sampler(&SamplerDescriptor {
142 label: Some("wgpu-mipmap-sampler"),
143 address_mode_u: AddressMode::ClampToEdge,
144 address_mode_v: AddressMode::ClampToEdge,
145 address_mode_w: AddressMode::ClampToEdge,
146 mag_filter: FilterMode::Linear,
147 min_filter: FilterMode::Nearest,
148 mipmap_filter: FilterMode::Nearest,
149 ..Default::default()
150 });
151
152 let render_layout_cache = {
153 let mut layout_cache = HashMap::new();
154 #[allow(clippy::single_element_loop)]
156 for &sample_type in &[TextureSampleType::Float { filterable: true }] {
157 let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
158 label: Some(&format!("wgpu-mipmap-bg-layout-{sample_type:?}")),
159 entries: &[
160 BindGroupLayoutEntry {
161 binding: 0,
162 visibility: wgpu::ShaderStages::FRAGMENT,
163 ty: BindingType::Texture {
164 view_dimension: TextureViewDimension::D2,
165 sample_type,
166 multisampled: false,
167 },
168 count: None,
169 },
170 BindGroupLayoutEntry {
171 binding: 1,
172 visibility: wgpu::ShaderStages::FRAGMENT,
173 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
174 count: None,
175 },
176 ],
177 });
178 layout_cache.insert(sample_type, bind_group_layout);
179 }
180 layout_cache
181 };
182
183 let render_pipeline_cache = {
184 let mut pipeline_cache = HashMap::new();
185 let vertex_module = device.create_shader_module(ShaderModuleDescriptor {
186 label: None,
187 source: make_spirv(include_bytes!("../shaders/triangle.vert.spv")),
188 });
189 let box_filter = device.create_shader_module(ShaderModuleDescriptor {
190 label: None,
191 source: make_spirv(include_bytes!("../shaders/box.frag.spv")),
192 });
193 for format in format_hints {
194 let fragment_module = &box_filter;
195
196 let sample_type = to_sample_type(*format);
197 if let Some(bind_group_layout) = render_layout_cache.get(&sample_type) {
198 let layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
199 label: None,
200 bind_group_layouts: &[bind_group_layout],
201 push_constant_ranges: &[],
202 });
203 let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
204 label: Some(&format!("wgpu-mipmap-render-pipeline-{format:?}")),
205 layout: Some(&layout),
206 vertex: VertexState {
207 module: &vertex_module,
208 entry_point: "main",
209 buffers: &[],
210 compilation_options: wgpu::PipelineCompilationOptions::default(),
211 },
212 primitive: PrimitiveState {
213 topology: wgpu::PrimitiveTopology::TriangleList,
214 front_face: FrontFace::Ccw,
215 cull_mode: Some(wgpu::Face::Back),
216 ..Default::default()
217 },
218 depth_stencil: None,
219 multisample: MultisampleState {
220 count: 1,
221 mask: !0,
222 alpha_to_coverage_enabled: false,
223 },
224 fragment: Some(FragmentState {
225 module: fragment_module,
226 entry_point: "main",
227 targets: &[Some(wgpu::ColorTargetState {
228 format: *format,
229 blend: None,
230 write_mask: wgpu::ColorWrites::ALL,
231 })],
232 compilation_options: wgpu::PipelineCompilationOptions::default(),
233 }),
234 multiview: None,
235 cache: None,
236 });
237 pipeline_cache.insert(*format, pipeline);
238 } else {
239 log::warn!("RenderMipmapGenerator does not support requested format {:?}", format);
240 continue;
241 }
242 }
243 pipeline_cache
244 };
245
246 Self {
247 sampler,
248 layout_cache: render_layout_cache,
249 pipeline_cache: render_pipeline_cache,
250 }
251 }
252
253 #[allow(clippy::too_many_arguments)]
257 #[allow(clippy::too_many_lines)]
258 pub(crate) fn generate_src_dst(
259 &self,
260 device: &Device,
261 encoder: &mut CommandEncoder,
262 src_texture: &Texture,
263 dst_texture: &Texture,
264 src_texture_descriptor: &TextureDescriptor,
265 dst_texture_descriptor: &TextureDescriptor,
266 dst_mip_offset: u32,
267 ) -> Result<(), Error> {
268 let src_format = src_texture_descriptor.format;
269 let src_mip_count = src_texture_descriptor.mip_level_count;
270 let src_ext = src_texture_descriptor.size;
271 let src_dim = src_texture_descriptor.dimension;
272 let src_usage = src_texture_descriptor.usage;
273 let src_next_mip_ext = get_mip_extent(&src_ext, 1);
274
275 let dst_format = dst_texture_descriptor.format;
276 let dst_mip_count = dst_texture_descriptor.mip_level_count;
277 let dst_ext = dst_texture_descriptor.size;
278 let dst_dim = dst_texture_descriptor.dimension;
279 let dst_usage = dst_texture_descriptor.usage;
280 if src_format != dst_format {
282 dbg!(src_texture_descriptor);
283 dbg!(dst_texture_descriptor);
284 panic!("src and dst texture formats must be equal");
285 }
286 if src_dim != dst_dim {
287 dbg!(src_texture_descriptor);
288 dbg!(dst_texture_descriptor);
289 panic!("src and dst texture dimensions must be eqaul");
290 }
291 if !((src_mip_count == dst_mip_count && src_ext == dst_ext) || (src_next_mip_ext == dst_ext)) {
292 dbg!(src_texture_descriptor);
293 dbg!(dst_texture_descriptor);
294 panic!("src and dst texture extents must match or dst must be half the size of src");
295 }
296
297 if src_dim != TextureDimension::D2 {
298 return Err(Error::UnsupportedDimension(src_dim));
299 }
300 if !src_usage.contains(TextureUsages::TEXTURE_BINDING) {
302 return Err(Error::UnsupportedUsage(src_usage));
303 }
304 if !dst_usage.contains(Self::required_usage()) {
306 return Err(Error::UnsupportedUsage(dst_usage));
307 }
308 let format = src_format;
309 let pipeline = self.pipeline_cache.get(&format).ok_or(Error::UnknownFormat(format))?;
310 let sample_type = to_sample_type(format);
311 let layout = self.layout_cache.get(&sample_type).ok_or(Error::UnknownFormat(format))?;
312 let views = (0..src_mip_count)
313 .map(|mip_level| {
314 let (texture, base_mip_level) = if mip_level == 0 {
317 (src_texture, 0)
318 } else {
319 (dst_texture, mip_level - dst_mip_offset)
320 };
321 texture.create_view(&TextureViewDescriptor {
322 label: None,
323 format: None,
324 dimension: None,
325 aspect: TextureAspect::All,
326 base_mip_level,
327 mip_level_count: Some(1),
328 array_layer_count: None,
329 base_array_layer: 0,
330 })
331 })
332 .collect::<Vec<_>>();
333 for mip in 1..src_mip_count as usize {
334 let src_view = &views[mip - 1];
335 let dst_view = &views[mip];
336 let bind_group = device.create_bind_group(&BindGroupDescriptor {
337 label: None,
338 layout,
339 entries: &[
340 BindGroupEntry {
341 binding: 0,
342 resource: BindingResource::TextureView(src_view),
343 },
344 BindGroupEntry {
345 binding: 1,
346 resource: BindingResource::Sampler(&self.sampler),
347 },
348 ],
349 });
350 let mut pass = encoder.begin_render_pass(&RenderPassDescriptor {
351 label: None,
352 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
353 view: dst_view,
354 resolve_target: None,
355 ops: Operations {
356 load: LoadOp::Load,
357 store: wgpu::StoreOp::Store,
358 },
359 })],
360 depth_stencil_attachment: None,
361 timestamp_writes: None,
362 occlusion_query_set: None,
363 });
364 pass.set_pipeline(pipeline);
365 pass.set_bind_group(0, &bind_group, &[]);
366 pass.draw(0..3, 0..1);
367 }
368 Ok(())
369 }
370}
371
372impl RenderMipmapGenerator {
373 pub fn generate(
377 &self,
378 device: &Device,
379 encoder: &mut CommandEncoder,
380 texture: &Texture,
381 texture_descriptor: &TextureDescriptor,
382 ) -> Result<(), Error> {
383 self.generate_src_dst(device, encoder, texture, texture, texture_descriptor, texture_descriptor, 0)
384 }
385}
386
387#[derive(Debug, Error, PartialEq, Eq)]
389pub enum Error {
390 #[error("Unsupported texture usage `{0:?}`.\nYour texture usage must contain one of: 1. TextureUsage::STORAGE, 2. TextureUsage::OUTPUT_ATTACHMENT | TextureUsage::SAMPLED, 3. TextureUsage::COPY_SRC | TextureUsage::COPY_DST")]
391 UnsupportedUsage(wgpu::TextureUsages),
392 #[error("Unsupported texture dimension `{0:?}. You texture dimension must be TextureDimension::D2`")]
393 UnsupportedDimension(wgpu::TextureDimension),
394 #[error("Unsupported texture format `{0:?}`. Try using the render backend.")]
395 UnsupportedFormat(wgpu::TextureFormat),
396 #[error("Unsupported texture size. Texture size must be a power of 2.")]
397 NpotTexture,
398 #[error("Unknown texture format `{0:?}`.\nDid you mean to specify it in `MipmapGeneratorDescriptor::formats`?")]
399 UnknownFormat(wgpu::TextureFormat),
400}