veloren_voxygen/render/renderer/
shadow_map.rs

1use super::super::{RenderError, ShadowMapMode, pipelines::shadow, texture::Texture};
2use vek::*;
3
4/// A type that holds shadow map data.  Since shadow mapping may not be
5/// supported on all platforms, we try to keep it separate.
6pub 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, // Cube texture
21        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                usage: None,
86                aspect: wgpu::TextureAspect::DepthOnly,
87                base_mip_level: 0,
88                mip_level_count: None,
89                base_array_layer: 0,
90                array_layer_count: None,
91            };
92
93            let sampler_info = wgpu::SamplerDescriptor {
94                label: None,
95                address_mode_u: wgpu::AddressMode::ClampToEdge,
96                address_mode_v: wgpu::AddressMode::ClampToEdge,
97                address_mode_w: wgpu::AddressMode::ClampToEdge,
98                mag_filter: wgpu::FilterMode::Linear,
99                min_filter: wgpu::FilterMode::Linear,
100                mipmap_filter: wgpu::FilterMode::Nearest,
101                compare: Some(wgpu::CompareFunction::LessEqual),
102                ..Default::default()
103            };
104
105            Texture::new_raw(device, &tex, &view, &sampler_info)
106        };
107
108        let cube_tex = make_tex(wgpu::TextureViewDimension::Cube, 6);
109        let tex = make_tex(wgpu::TextureViewDimension::D2, 1);
110
111        // Clear to 1.0
112        let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
113            label: Some("Dummy shadow tex clearing encoder"),
114        });
115        let mut clear = |tex: &Texture| {
116            encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
117                label: Some("Clear dummy shadow texture"),
118                color_attachments: &[],
119                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
120                    view: &tex.view,
121                    depth_ops: Some(wgpu::Operations {
122                        load: wgpu::LoadOp::Clear(1.0),
123                        store: wgpu::StoreOp::Store,
124                    }),
125                    stencil_ops: None,
126                }),
127                timestamp_writes: None,
128                occlusion_query_set: None,
129            });
130        };
131        clear(&cube_tex);
132        clear(&tex);
133        #[expect(clippy::drop_non_drop)]
134        drop(clear);
135        queue.submit(std::iter::once(encoder.finish()));
136
137        (cube_tex, tex)
138    }
139
140    /// Create textures and views for shadow maps.
141    /// Returns (point, directed)
142    pub(super) fn create_shadow_views(
143        device: &wgpu::Device,
144        size: (u32, u32),
145        mode: &ShadowMapMode,
146        max_texture_size: u32,
147    ) -> Result<(Texture, Texture), RenderError> {
148        // (Attempt to) apply resolution factor to shadow map resolution.
149        let resolution_factor = mode.resolution.clamped(0.25, 4.0);
150
151        // Limit to max texture size, rather than erroring.
152        let size = Vec2::new(size.0, size.1).map(|e| {
153            let size = e as f32 * resolution_factor;
154            // NOTE: We know 0 <= e since we clamped the resolution factor to be between
155            // 0.25 and 4.0.
156            if size <= max_texture_size as f32 {
157                size as u32
158            } else {
159                max_texture_size
160            }
161        });
162
163        let levels = 1;
164        // Limit to max texture size rather than erroring.
165        let two_size = size.map(|e| {
166            u32::checked_next_power_of_two(e)
167                .filter(|&e| e <= max_texture_size)
168                .unwrap_or(max_texture_size)
169        });
170        let min_size = size.reduce_min();
171        let max_size = size.reduce_max();
172        let _min_two_size = two_size.reduce_min();
173        let _max_two_size = two_size.reduce_max();
174        // For rotated shadow maps, the maximum size of a pixel along any axis is the
175        // size of a diagonal along that axis.
176        let diag_size = size.map(f64::from).magnitude();
177        let diag_cross_size = f64::from(min_size) / f64::from(max_size) * diag_size;
178        let (diag_size, _diag_cross_size) =
179            if 0.0 < diag_size && diag_size <= f64::from(max_texture_size) {
180                // NOTE: diag_cross_size must be non-negative, since it is the ratio of a
181                // non-negative and a positive number (if max_size were zero,
182                // diag_size would be 0 too).  And it must be <= diag_size,
183                // since min_size <= max_size.  Therefore, if diag_size fits in a
184                // u16, so does diag_cross_size.
185                (diag_size as u32, diag_cross_size as u32)
186            } else {
187                // Limit to max texture resolution rather than error.
188                (max_texture_size, max_texture_size)
189            };
190        let diag_two_size = u32::checked_next_power_of_two(diag_size)
191            .filter(|&e| e <= max_texture_size)
192            // Limit to max texture resolution rather than error.
193            .unwrap_or(max_texture_size)
194            // Make sure we don't try to create a zero sized texture (divided by 4 below)
195            .max(4);
196
197        let point_shadow_tex = wgpu::TextureDescriptor {
198            label: None,
199            size: wgpu::Extent3d {
200                width: diag_two_size / 4,
201                height: diag_two_size / 4,
202                depth_or_array_layers: 6,
203            },
204            mip_level_count: levels,
205            sample_count: 1,
206            dimension: wgpu::TextureDimension::D2,
207            format: wgpu::TextureFormat::Depth24Plus,
208            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
209            view_formats: &[],
210        };
211
212        let point_shadow_view = wgpu::TextureViewDescriptor {
213            label: None,
214            format: Some(wgpu::TextureFormat::Depth24Plus),
215            dimension: Some(wgpu::TextureViewDimension::Cube),
216            usage: None,
217            aspect: wgpu::TextureAspect::DepthOnly,
218            base_mip_level: 0,
219            mip_level_count: None,
220            base_array_layer: 0,
221            array_layer_count: None,
222        };
223
224        let directed_shadow_tex = wgpu::TextureDescriptor {
225            label: None,
226            size: wgpu::Extent3d {
227                width: diag_two_size,
228                height: diag_two_size,
229                depth_or_array_layers: 1,
230            },
231            mip_level_count: levels,
232            sample_count: 1,
233            dimension: wgpu::TextureDimension::D2,
234            format: wgpu::TextureFormat::Depth24Plus,
235            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
236            view_formats: &[],
237        };
238
239        let directed_shadow_view = wgpu::TextureViewDescriptor {
240            label: None,
241            format: Some(wgpu::TextureFormat::Depth24Plus),
242            dimension: Some(wgpu::TextureViewDimension::D2),
243            usage: None,
244            aspect: wgpu::TextureAspect::DepthOnly,
245            base_mip_level: 0,
246            mip_level_count: None,
247            base_array_layer: 0,
248            array_layer_count: None,
249        };
250
251        let sampler_info = wgpu::SamplerDescriptor {
252            label: None,
253            address_mode_u: wgpu::AddressMode::ClampToEdge,
254            address_mode_v: wgpu::AddressMode::ClampToEdge,
255            address_mode_w: wgpu::AddressMode::ClampToEdge,
256            mag_filter: wgpu::FilterMode::Linear,
257            min_filter: wgpu::FilterMode::Linear,
258            mipmap_filter: wgpu::FilterMode::Nearest,
259            compare: Some(wgpu::CompareFunction::LessEqual),
260            ..Default::default()
261        };
262
263        let point_shadow_tex =
264            Texture::new_raw(device, &point_shadow_tex, &point_shadow_view, &sampler_info);
265        let directed_shadow_tex = Texture::new_raw(
266            device,
267            &directed_shadow_tex,
268            &directed_shadow_view,
269            &sampler_info,
270        );
271
272        Ok((point_shadow_tex, directed_shadow_tex))
273    }
274
275    pub fn textures(&self) -> (&Texture, &Texture) {
276        match self {
277            Self::Enabled(renderer) => (&renderer.point_depth, &renderer.directed_depth),
278            Self::Disabled {
279                dummy_point,
280                dummy_directed,
281            } => (dummy_point, dummy_directed),
282        }
283    }
284
285    pub fn is_enabled(&self) -> bool { matches!(self, Self::Enabled(_)) }
286}