1use 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 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}