1use super::super::{RenderError, ShadowMapMode, pipelines::shadow, texture::Texture};
2use vek::*;
3
4pub struct ShadowMapRenderer {
7 pub directed_depth: Texture,
8
9 pub point_depth: Texture,
10
11 pub point_pipeline: shadow::PointShadowPipeline,
12 pub terrain_directed_pipeline: shadow::ShadowPipeline,
13 pub figure_directed_pipeline: shadow::ShadowFigurePipeline,
14 pub debug_directed_pipeline: shadow::ShadowDebugPipeline,
15}
16
17pub enum ShadowMap {
18 Enabled(ShadowMapRenderer),
19 Disabled {
20 dummy_point: Texture, dummy_directed: Texture,
22 },
23}
24
25impl ShadowMap {
26 pub fn new(
27 device: &wgpu::Device,
28 queue: &wgpu::Queue,
29 point: Option<shadow::PointShadowPipeline>,
30 directed: Option<shadow::ShadowPipeline>,
31 figure: Option<shadow::ShadowFigurePipeline>,
32 debug: Option<shadow::ShadowDebugPipeline>,
33 shadow_views: Option<(Texture, Texture)>,
34 ) -> Self {
35 if let (
36 Some(point_pipeline),
37 Some(terrain_directed_pipeline),
38 Some(figure_directed_pipeline),
39 Some(debug_directed_pipeline),
40 Some(shadow_views),
41 ) = (point, directed, figure, debug, shadow_views)
42 {
43 let (point_depth, directed_depth) = shadow_views;
44
45 Self::Enabled(ShadowMapRenderer {
46 directed_depth,
47 point_depth,
48
49 point_pipeline,
50 terrain_directed_pipeline,
51 figure_directed_pipeline,
52 debug_directed_pipeline,
53 })
54 } else {
55 let (dummy_point, dummy_directed) = Self::create_dummy_shadow_tex(device, queue);
56 Self::Disabled {
57 dummy_point,
58 dummy_directed,
59 }
60 }
61 }
62
63 fn create_dummy_shadow_tex(device: &wgpu::Device, queue: &wgpu::Queue) -> (Texture, Texture) {
64 let make_tex = |view_dim, depth| {
65 let tex = wgpu::TextureDescriptor {
66 label: None,
67 size: wgpu::Extent3d {
68 width: 4,
69 height: 4,
70 depth_or_array_layers: depth,
71 },
72 mip_level_count: 1,
73 sample_count: 1,
74 dimension: wgpu::TextureDimension::D2,
75 format: wgpu::TextureFormat::Depth24Plus,
76 usage: wgpu::TextureUsages::TEXTURE_BINDING
77 | wgpu::TextureUsages::RENDER_ATTACHMENT,
78 view_formats: &[],
79 };
80
81 let view = wgpu::TextureViewDescriptor {
82 label: None,
83 format: Some(wgpu::TextureFormat::Depth24Plus),
84 dimension: Some(view_dim),
85 aspect: wgpu::TextureAspect::DepthOnly,
86 base_mip_level: 0,
87 mip_level_count: None,
88 base_array_layer: 0,
89 array_layer_count: None,
90 };
91
92 let sampler_info = wgpu::SamplerDescriptor {
93 label: None,
94 address_mode_u: wgpu::AddressMode::ClampToEdge,
95 address_mode_v: wgpu::AddressMode::ClampToEdge,
96 address_mode_w: wgpu::AddressMode::ClampToEdge,
97 mag_filter: wgpu::FilterMode::Linear,
98 min_filter: wgpu::FilterMode::Linear,
99 mipmap_filter: wgpu::FilterMode::Nearest,
100 compare: Some(wgpu::CompareFunction::LessEqual),
101 ..Default::default()
102 };
103
104 Texture::new_raw(device, &tex, &view, &sampler_info)
105 };
106
107 let cube_tex = make_tex(wgpu::TextureViewDimension::Cube, 6);
108 let tex = make_tex(wgpu::TextureViewDimension::D2, 1);
109
110 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
112 label: Some("Dummy shadow tex clearing encoder"),
113 });
114 let mut clear = |tex: &Texture| {
115 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
116 label: Some("Clear dummy shadow texture"),
117 color_attachments: &[],
118 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
119 view: &tex.view,
120 depth_ops: Some(wgpu::Operations {
121 load: wgpu::LoadOp::Clear(1.0),
122 store: wgpu::StoreOp::Store,
123 }),
124 stencil_ops: None,
125 }),
126 timestamp_writes: None,
127 occlusion_query_set: None,
128 });
129 };
130 clear(&cube_tex);
131 clear(&tex);
132 #[expect(clippy::drop_non_drop)]
133 drop(clear);
134 queue.submit(std::iter::once(encoder.finish()));
135
136 (cube_tex, tex)
137 }
138
139 pub(super) fn create_shadow_views(
142 device: &wgpu::Device,
143 size: (u32, u32),
144 mode: &ShadowMapMode,
145 max_texture_size: u32,
146 ) -> Result<(Texture, Texture), RenderError> {
147 let resolution_factor = mode.resolution.clamped(0.25, 4.0);
149
150 let size = Vec2::new(size.0, size.1).map(|e| {
152 let size = e as f32 * resolution_factor;
153 if size <= max_texture_size as f32 {
156 size as u32
157 } else {
158 max_texture_size
159 }
160 });
161
162 let levels = 1;
163 let two_size = size.map(|e| {
165 u32::checked_next_power_of_two(e)
166 .filter(|&e| e <= max_texture_size)
167 .unwrap_or(max_texture_size)
168 });
169 let min_size = size.reduce_min();
170 let max_size = size.reduce_max();
171 let _min_two_size = two_size.reduce_min();
172 let _max_two_size = two_size.reduce_max();
173 let diag_size = size.map(f64::from).magnitude();
176 let diag_cross_size = f64::from(min_size) / f64::from(max_size) * diag_size;
177 let (diag_size, _diag_cross_size) =
178 if 0.0 < diag_size && diag_size <= f64::from(max_texture_size) {
179 (diag_size as u32, diag_cross_size as u32)
185 } else {
186 (max_texture_size, max_texture_size)
188 };
189 let diag_two_size = u32::checked_next_power_of_two(diag_size)
190 .filter(|&e| e <= max_texture_size)
191 .unwrap_or(max_texture_size)
193 .max(4);
195
196 let point_shadow_tex = wgpu::TextureDescriptor {
197 label: None,
198 size: wgpu::Extent3d {
199 width: diag_two_size / 4,
200 height: diag_two_size / 4,
201 depth_or_array_layers: 6,
202 },
203 mip_level_count: levels,
204 sample_count: 1,
205 dimension: wgpu::TextureDimension::D2,
206 format: wgpu::TextureFormat::Depth24Plus,
207 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
208 view_formats: &[],
209 };
210
211 let point_shadow_view = wgpu::TextureViewDescriptor {
212 label: None,
213 format: Some(wgpu::TextureFormat::Depth24Plus),
214 dimension: Some(wgpu::TextureViewDimension::Cube),
215 aspect: wgpu::TextureAspect::DepthOnly,
216 base_mip_level: 0,
217 mip_level_count: None,
218 base_array_layer: 0,
219 array_layer_count: None,
220 };
221
222 let directed_shadow_tex = wgpu::TextureDescriptor {
223 label: None,
224 size: wgpu::Extent3d {
225 width: diag_two_size,
226 height: diag_two_size,
227 depth_or_array_layers: 1,
228 },
229 mip_level_count: levels,
230 sample_count: 1,
231 dimension: wgpu::TextureDimension::D2,
232 format: wgpu::TextureFormat::Depth24Plus,
233 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
234 view_formats: &[],
235 };
236
237 let directed_shadow_view = wgpu::TextureViewDescriptor {
238 label: None,
239 format: Some(wgpu::TextureFormat::Depth24Plus),
240 dimension: Some(wgpu::TextureViewDimension::D2),
241 aspect: wgpu::TextureAspect::DepthOnly,
242 base_mip_level: 0,
243 mip_level_count: None,
244 base_array_layer: 0,
245 array_layer_count: None,
246 };
247
248 let sampler_info = wgpu::SamplerDescriptor {
249 label: None,
250 address_mode_u: wgpu::AddressMode::ClampToEdge,
251 address_mode_v: wgpu::AddressMode::ClampToEdge,
252 address_mode_w: wgpu::AddressMode::ClampToEdge,
253 mag_filter: wgpu::FilterMode::Linear,
254 min_filter: wgpu::FilterMode::Linear,
255 mipmap_filter: wgpu::FilterMode::Nearest,
256 compare: Some(wgpu::CompareFunction::LessEqual),
257 ..Default::default()
258 };
259
260 let point_shadow_tex =
261 Texture::new_raw(device, &point_shadow_tex, &point_shadow_view, &sampler_info);
262 let directed_shadow_tex = Texture::new_raw(
263 device,
264 &directed_shadow_tex,
265 &directed_shadow_view,
266 &sampler_info,
267 );
268
269 Ok((point_shadow_tex, directed_shadow_tex))
270 }
271
272 pub fn textures(&self) -> (&Texture, &Texture) {
273 match self {
274 Self::Enabled(renderer) => (&renderer.point_depth, &renderer.directed_depth),
275 Self::Disabled {
276 dummy_point,
277 dummy_directed,
278 } => (dummy_point, dummy_directed),
279 }
280 }
281
282 pub fn is_enabled(&self) -> bool { matches!(self, Self::Enabled(_)) }
283}