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                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        // Clear to 1.0
111        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    /// Create textures and views for shadow maps.
140    /// Returns (point, directed)
141    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        // (Attempt to) apply resolution factor to shadow map resolution.
148        let resolution_factor = mode.resolution.clamped(0.25, 4.0);
149
150        // Limit to max texture size, rather than erroring.
151        let size = Vec2::new(size.0, size.1).map(|e| {
152            let size = e as f32 * resolution_factor;
153            // NOTE: We know 0 <= e since we clamped the resolution factor to be between
154            // 0.25 and 4.0.
155            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        // Limit to max texture size rather than erroring.
164        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        // For rotated shadow maps, the maximum size of a pixel along any axis is the
174        // size of a diagonal along that axis.
175        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                // NOTE: diag_cross_size must be non-negative, since it is the ratio of a
180                // non-negative and a positive number (if max_size were zero,
181                // diag_size would be 0 too).  And it must be <= diag_size,
182                // since min_size <= max_size.  Therefore, if diag_size fits in a
183                // u16, so does diag_cross_size.
184                (diag_size as u32, diag_cross_size as u32)
185            } else {
186                // Limit to max texture resolution rather than error.
187                (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            // Limit to max texture resolution rather than error.
192            .unwrap_or(max_texture_size)
193            // Make sure we don't try to create a zero sized texture (divided by 4 below)
194            .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}