1use crate::core::*;
2use crate::util::get_mip_extent;
3use log::warn;
4use std::collections::HashMap;
5
6#[derive(Debug)]
8pub struct RenderMipmapGenerator {
9 sampler: wgpu::Sampler,
10 layout_cache: HashMap<wgpu::TextureComponentType, wgpu::BindGroupLayout>,
11 pipeline_cache: HashMap<wgpu::TextureFormat, wgpu::RenderPipeline>,
12}
13
14impl RenderMipmapGenerator {
15 pub fn required_usage() -> wgpu::TextureUsage {
17 wgpu::TextureUsage::OUTPUT_ATTACHMENT | wgpu::TextureUsage::SAMPLED
18 }
19
20 pub fn new_with_format_hints(
23 device: &wgpu::Device,
24 format_hints: &[wgpu::TextureFormat],
25 ) -> Self {
26 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
32 label: Some(&"wgpu-mipmap-sampler"),
33 address_mode_u: wgpu::AddressMode::ClampToEdge,
34 address_mode_v: wgpu::AddressMode::ClampToEdge,
35 address_mode_w: wgpu::AddressMode::ClampToEdge,
36 mag_filter: wgpu::FilterMode::Linear,
37 min_filter: wgpu::FilterMode::Nearest,
38 mipmap_filter: wgpu::FilterMode::Nearest,
39 ..Default::default()
40 });
41
42 let render_layout_cache = {
43 let mut layout_cache = HashMap::new();
44 for component_type in &[wgpu::TextureComponentType::Float] {
46 let bind_group_layout =
47 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
48 label: Some(&format!("wgpu-mipmap-bg-layout-{:?}", component_type)),
49 entries: &[
50 wgpu::BindGroupLayoutEntry {
51 binding: 0,
52 visibility: wgpu::ShaderStage::FRAGMENT,
53 ty: wgpu::BindingType::SampledTexture {
54 dimension: wgpu::TextureViewDimension::D2,
55 component_type: *component_type,
56 multisampled: false,
57 },
58 count: None,
59 },
60 wgpu::BindGroupLayoutEntry {
61 binding: 1,
62 visibility: wgpu::ShaderStage::FRAGMENT,
63 ty: wgpu::BindingType::Sampler { comparison: false },
64 count: None,
65 },
66 ],
67 });
68 layout_cache.insert(*component_type, bind_group_layout);
69 }
70 layout_cache
71 };
72
73 let render_pipeline_cache = {
74 let mut pipeline_cache = HashMap::new();
75 let vertex_module = device.create_shader_module(wgpu::util::make_spirv(
76 include_bytes!("shaders/triangle.vert.spv"),
77 ));
78 let box_filter = device.create_shader_module(wgpu::util::make_spirv(include_bytes!(
79 "shaders/box.frag.spv"
80 )));
81 for format in format_hints {
82 let fragment_module = &box_filter;
83
84 let component_type = wgpu::TextureComponentType::from(*format);
85 if let Some(bind_group_layout) = render_layout_cache.get(&component_type) {
86 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
87 label: None,
88 bind_group_layouts: &[bind_group_layout],
89 push_constant_ranges: &[],
90 });
91 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
92 label: Some(&format!("wgpu-mipmap-render-pipeline-{:?}", format)),
93 layout: Some(&layout),
94 vertex_stage: wgpu::ProgrammableStageDescriptor {
95 module: &vertex_module,
96 entry_point: "main",
97 },
98 fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
99 module: &fragment_module,
100 entry_point: "main",
101 }),
102 rasterization_state: Some(wgpu::RasterizationStateDescriptor {
103 front_face: wgpu::FrontFace::Ccw,
104 cull_mode: wgpu::CullMode::Back,
105 ..Default::default()
106 }),
107 primitive_topology: wgpu::PrimitiveTopology::TriangleList,
108 color_states: &[(*format).into()],
109 depth_stencil_state: None,
110 vertex_state: wgpu::VertexStateDescriptor {
111 index_format: wgpu::IndexFormat::Uint16,
112 vertex_buffers: &[],
113 },
114 sample_count: 1,
115 sample_mask: !0,
116 alpha_to_coverage_enabled: false,
117 });
118 pipeline_cache.insert(*format, pipeline);
119 } else {
120 warn!(
121 "RenderMipmapGenerator does not support requested format {:?}",
122 format
123 );
124 continue;
125 }
126 }
127 pipeline_cache
128 };
129
130 Self {
131 sampler,
132 layout_cache: render_layout_cache,
133 pipeline_cache: render_pipeline_cache,
134 }
135 }
136
137 #[allow(clippy::too_many_arguments)]
141 pub(crate) fn generate_src_dst(
142 &self,
143 device: &wgpu::Device,
144 encoder: &mut wgpu::CommandEncoder,
145 src_texture: &wgpu::Texture,
146 dst_texture: &wgpu::Texture,
147 src_texture_descriptor: &wgpu::TextureDescriptor,
148 dst_texture_descriptor: &wgpu::TextureDescriptor,
149 dst_mip_offset: u32,
150 ) -> Result<(), Error> {
151 let src_format = src_texture_descriptor.format;
152 let src_mip_count = src_texture_descriptor.mip_level_count;
153 let src_ext = src_texture_descriptor.size;
154 let src_dim = src_texture_descriptor.dimension;
155 let src_usage = src_texture_descriptor.usage;
156 let src_next_mip_ext = get_mip_extent(&src_ext, 1);
157
158 let dst_format = dst_texture_descriptor.format;
159 let dst_mip_count = dst_texture_descriptor.mip_level_count;
160 let dst_ext = dst_texture_descriptor.size;
161 let dst_dim = dst_texture_descriptor.dimension;
162 let dst_usage = dst_texture_descriptor.usage;
163 if src_format != dst_format {
165 dbg!(src_texture_descriptor);
166 dbg!(dst_texture_descriptor);
167 panic!("src and dst texture formats must be equal");
168 }
169 if src_dim != dst_dim {
170 dbg!(src_texture_descriptor);
171 dbg!(dst_texture_descriptor);
172 panic!("src and dst texture dimensions must be eqaul");
173 }
174 if !((src_mip_count == dst_mip_count && src_ext == dst_ext)
175 || (src_next_mip_ext == dst_ext))
176 {
177 dbg!(src_texture_descriptor);
178 dbg!(dst_texture_descriptor);
179 panic!("src and dst texture extents must match or dst must be half the size of src");
180 }
181
182 if src_dim != wgpu::TextureDimension::D2 {
183 return Err(Error::UnsupportedDimension(src_dim));
184 }
185 if !src_usage.contains(wgpu::TextureUsage::SAMPLED) {
187 return Err(Error::UnsupportedUsage(src_usage));
188 }
189 if !dst_usage.contains(Self::required_usage()) {
191 return Err(Error::UnsupportedUsage(dst_usage));
192 }
193 let format = src_format;
194 let pipeline = self
195 .pipeline_cache
196 .get(&format)
197 .ok_or(Error::UnknownFormat(format))?;
198 let component_type = wgpu::TextureComponentType::from(format);
199 let layout = self
200 .layout_cache
201 .get(&component_type)
202 .ok_or(Error::UnknownFormat(format))?;
203 let views = (0..src_mip_count)
204 .map(|mip_level| {
205 let (texture, base_mip_level) = if mip_level == 0 {
208 (src_texture, 0)
209 } else {
210 (dst_texture, mip_level - dst_mip_offset)
211 };
212 texture.create_view(&wgpu::TextureViewDescriptor {
213 label: None,
214 format: None,
215 dimension: None,
216 aspect: wgpu::TextureAspect::All,
217 base_mip_level,
218 level_count: std::num::NonZeroU32::new(1),
219 array_layer_count: None,
220 base_array_layer: 0,
221 })
222 })
223 .collect::<Vec<_>>();
224 for mip in 1..src_mip_count as usize {
225 let src_view = &views[mip - 1];
226 let dst_view = &views[mip];
227 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
228 label: None,
229 layout,
230 entries: &[
231 wgpu::BindGroupEntry {
232 binding: 0,
233 resource: wgpu::BindingResource::TextureView(&src_view),
234 },
235 wgpu::BindGroupEntry {
236 binding: 1,
237 resource: wgpu::BindingResource::Sampler(&self.sampler),
238 },
239 ],
240 });
241 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
242 color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
243 attachment: &dst_view,
244 resolve_target: None,
245 ops: wgpu::Operations {
246 load: wgpu::LoadOp::Load,
247 store: true,
248 },
249 }],
250 depth_stencil_attachment: None,
251 });
252 pass.set_pipeline(pipeline);
253 pass.set_bind_group(0, &bind_group, &[]);
254 pass.draw(0..3, 0..1);
255 }
256 Ok(())
257 }
258}
259
260impl MipmapGenerator for RenderMipmapGenerator {
261 fn generate(
262 &self,
263 device: &wgpu::Device,
264 encoder: &mut wgpu::CommandEncoder,
265 texture: &wgpu::Texture,
266 texture_descriptor: &wgpu::TextureDescriptor,
267 ) -> Result<(), Error> {
268 self.generate_src_dst(
269 device,
270 encoder,
271 &texture,
272 &texture,
273 &texture_descriptor,
274 &texture_descriptor,
275 0,
276 )
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283 use crate::util::*;
284
285 fn init() {
286 let _ = env_logger::builder().is_test(true).try_init();
287 }
288
289 #[allow(dead_code)]
290 async fn generate_and_copy_to_cpu_render(
291 buffer: &[u8],
292 texture_descriptor: &wgpu::TextureDescriptor<'_>,
293 ) -> Result<Vec<MipBuffer>, Error> {
294 let (_instance, _adaptor, device, queue) = wgpu_setup().await;
295 let generator = crate::backends::RenderMipmapGenerator::new_with_format_hints(
296 &device,
297 &[texture_descriptor.format],
298 );
299 Ok(
300 generate_and_copy_to_cpu(&device, &queue, &generator, buffer, texture_descriptor)
301 .await?,
302 )
303 }
304
305 async fn generate_test(texture_descriptor: &wgpu::TextureDescriptor<'_>) -> Result<(), Error> {
306 let (_instance, _adapter, device, _queue) = wgpu_setup().await;
307 let generator =
308 RenderMipmapGenerator::new_with_format_hints(&device, &[texture_descriptor.format]);
309 let texture = device.create_texture(&texture_descriptor);
310 let mut encoder = device.create_command_encoder(&Default::default());
311 generator.generate(&device, &mut encoder, &texture, &texture_descriptor)
312 }
313
314 #[test]
315 fn sanity_check() {
316 init();
317 let size = 512;
319 let mip_level_count = 1 + (size as f32).log2() as u32;
320 let format = wgpu::TextureFormat::R8Unorm;
322 let texture_extent = wgpu::Extent3d {
323 width: size,
324 height: size,
325 depth: 1,
326 };
327 let texture_descriptor = wgpu::TextureDescriptor {
328 size: texture_extent,
329 mip_level_count,
330 format,
331 sample_count: 1,
332 dimension: wgpu::TextureDimension::D2,
333 usage: RenderMipmapGenerator::required_usage(),
334 label: None,
335 };
336 futures::executor::block_on((|| async {
337 let res = generate_test(&texture_descriptor).await;
338 assert!(res.is_ok());
339 })());
340 }
341
342 #[test]
343 fn unsupported_usage() {
344 init();
345 let size = 512;
347 let mip_level_count = 1 + (size as f32).log2() as u32;
348 let format = wgpu::TextureFormat::R8Unorm;
350 let texture_extent = wgpu::Extent3d {
351 width: size,
352 height: size,
353 depth: 1,
354 };
355 let texture_descriptor = wgpu::TextureDescriptor {
356 size: texture_extent,
357 mip_level_count,
358 format,
359 sample_count: 1,
360 dimension: wgpu::TextureDimension::D2,
361 usage: wgpu::TextureUsage::empty(),
362 label: None,
363 };
364 futures::executor::block_on((|| async {
365 let res = generate_test(&texture_descriptor).await;
366 assert!(res.is_err());
367 assert!(res.err() == Some(Error::UnsupportedUsage(wgpu::TextureUsage::empty())));
368 })());
369 }
370
371 #[test]
372 fn unknown_format() {
373 init();
374 let size = 512;
376 let mip_level_count = 1 + (size as f32).log2() as u32;
377 let format = wgpu::TextureFormat::Rgba8Sint;
379 let texture_extent = wgpu::Extent3d {
380 width: size,
381 height: size,
382 depth: 1,
383 };
384 let texture_descriptor = wgpu::TextureDescriptor {
385 size: texture_extent,
386 mip_level_count,
387 format,
388 sample_count: 1,
389 dimension: wgpu::TextureDimension::D2,
390 usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::OUTPUT_ATTACHMENT,
391 label: None,
392 };
393 futures::executor::block_on((|| async {
394 let res = generate_test(&texture_descriptor).await;
395 assert!(res.is_err());
396 assert!(res.err() == Some(Error::UnknownFormat(wgpu::TextureFormat::Rgba8Sint)));
397 })());
398 }
399}