gloss_renderer/forward_renderer/render_passes/
shadow_pass.rs

1use crate::{
2    components::{FacesGPU, LightEmit, ModelMatrix, Name, PosLookat, Renderable, ShadowCaster, ShadowMap, ShadowMapDirty, VertsGPU, VisMesh},
3    scene::Scene,
4};
5use easy_wgpu::{
6    bind_group::{BindGroupBuilder, BindGroupDesc, BindGroupWrapper},
7    bind_group_layout::{BindGroupLayoutBuilder, BindGroupLayoutDesc},
8    buffer::Buffer,
9    gpu::Gpu,
10    pipeline::RenderPipelineDescBuilder,
11};
12use gloss_hecs::{Changed, CommandBuffer, Entity, Query, QueryBorrow};
13use gloss_utils::numerical::{align, align_usz};
14use log::debug;
15use std::collections::HashMap;
16
17/// Shadow pass which renders depth maps for each of the lights. Only updates
18/// the depth map when the vertices on the gpu have changed.
19pub struct ShadowPass {
20    render_pipeline: wgpu::RenderPipeline,
21    locals_uniform: LocalsUniform,                // a uniform buffer that we suballocate for the locals of every mesh
22    iterator_light_uniform: IteratorLightUniform, //a uniform buffer that we allocate only an index to use for indexing into our locals
23    command_buffer: CommandBuffer,
24}
25use super::upload_pass::PerFrameUniforms;
26
27//shaders
28#[include_wgsl_oil::include_wgsl_oil("../../../shaders/shadow_map.wgsl")]
29mod shader_code {}
30
31impl ShadowPass {
32    pub fn new(gpu: &Gpu) -> Self {
33        //render pipeline
34        let render_pipeline = RenderPipelineDescBuilder::new()
35            .label("shadow pass pipeline")
36            .shader_code(shader_code::SOURCE)
37            .add_bind_group_layout_desc(PerFrameUniforms::build_layout_desc())
38            .add_bind_group_layout_desc(IteratorLightUniform::layout_desc())
39            .add_bind_group_layout_desc(LocalsUniform::layout_desc())
40            .add_vertex_buffer_layout(VertsGPU::vertex_buffer_layout::<0>())
41            .depth_state(Some(wgpu::DepthStencilState {
42                format: wgpu::TextureFormat::Depth32Float,
43                depth_write_enabled: true,
44                depth_compare: wgpu::CompareFunction::Greater,
45                stencil: wgpu::StencilState::default(),
46                bias: wgpu::DepthBiasState::default(),
47            }))
48            .multisample(wgpu::MultisampleState::default())
49            .build_pipeline(gpu.device());
50
51        let locals_uniform = LocalsUniform::new(gpu);
52
53        let iterator_light_uniform = IteratorLightUniform::new(gpu);
54
55        let command_buffer = CommandBuffer::new();
56
57        Self {
58            render_pipeline,
59            locals_uniform,
60            iterator_light_uniform,
61            command_buffer,
62        }
63    }
64
65    pub fn run(&mut self, gpu: &Gpu, per_frame_uniforms: &PerFrameUniforms, scene: &mut Scene) {
66        self.begin_pass();
67
68        self.render_shadows(gpu, per_frame_uniforms, scene);
69
70        self.end_pass(scene);
71    }
72
73    fn render_shadows(&mut self, gpu: &Gpu, per_frame_uniforms: &PerFrameUniforms, scene: &Scene) {
74        let shadow_map_requires_update = self.check_shadow_maps_dirty(scene);
75        debug!("shadow_map_requires_update {shadow_map_requires_update}");
76        if !shadow_map_requires_update {
77            return; //nothing to do
78        }
79
80        let mut query_all_renderables = scene.world.query::<&Renderable>();
81        let mut query_meshes_for_shadow = scene.world.query::<(&VertsGPU, &FacesGPU, &VisMesh)>().with::<&Renderable>();
82
83        //upload to gpu the local information for each mesh like model matrix
84        self.update_locals(gpu, &mut query_all_renderables, scene);
85
86        //check for every light if it needs update and if it does, render all the
87        // meshes towards it's shadowmap
88        for (entity_light, shadow_map) in &mut scene.world.query::<&ShadowMap>().with::<(&LightEmit, &ShadowCaster)>() {
89            //do the actual rendering now
90            let mut encoder = gpu.device().create_command_encoder(&wgpu::CommandEncoderDescriptor {
91                label: Some("Shadow pass encoder"),
92            });
93            {
94                let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
95                    label: Some("Shadow Pass"),
96                    color_attachments: &[
97                        //depth moments
98                        // Some(wgpu::RenderPassColorAttachment {
99                        //     view: &shadow_map.tex_depth_moments.view,
100                        //     resolve_target: None,
101                        //     ops: wgpu::Operations {
102                        //         load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
103                        //         store: true,
104                        //     },
105                        // }),
106                    ],
107                    // depth_stencil_attachment: None,
108                    depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
109                        view: &shadow_map.tex_depth.view,
110                        depth_ops: Some(wgpu::Operations {
111                            load: wgpu::LoadOp::Clear(0.0),
112                            store: wgpu::StoreOp::Store,
113                        }),
114                        stencil_ops: None,
115                    }),
116                    timestamp_writes: None,
117                    occlusion_query_set: None,
118                });
119                render_pass.set_pipeline(&self.render_pipeline);
120
121                //gloal binding
122                render_pass.set_bind_group(0, &per_frame_uniforms.bind_group, &[]);
123
124                //iterator the light, we need to pass this idx into a buffer so we can access
125                // it in a shader
126                let light_name = scene.get_comp::<&Name>(&entity_light).unwrap().0.clone();
127                let light_idx = per_frame_uniforms.light2idx_ubo[&light_name];
128                self.iterator_light_uniform.set(gpu, light_idx); //writes to gpu
129                render_pass.set_bind_group(1, self.iterator_light_uniform.bind_group.bg(), &[]);
130
131                for (entity_mesh, (verts, faces, vis)) in query_meshes_for_shadow.iter() {
132                    if !vis.show_mesh {
133                        continue;
134                    }
135
136                    //local bindings
137                    let name = scene.get_comp::<&Name>(&entity_mesh).unwrap().0.clone();
138                    let (local_bg, offset) = &self.locals_uniform.mesh2local_bind[&name];
139                    render_pass.set_bind_group(2, local_bg.bg(), &[*offset]);
140                    // println!("Rendering mesh to shadow map {}", name);
141
142                    render_pass.set_vertex_buffer(0, verts.buf.slice(..));
143                    render_pass.set_index_buffer(faces.buf.slice(..), wgpu::IndexFormat::Uint32);
144                    render_pass.draw_indexed(0..faces.nr_triangles * 3, 0, 0..1);
145                }
146
147                //TODO render points
148            }
149            debug!("shadow encoder");
150            gpu.queue().submit(Some(encoder.finish()));
151        }
152    }
153
154    fn begin_pass(&mut self) {
155        // self.locals_uniform.buffer.reset_chunks_offset();
156        self.locals_uniform.buffer.reset_chunks_offset_if_necessary();
157    }
158
159    fn end_pass(&mut self, scene: &mut Scene) {
160        //remove all dirty components from entities
161        {
162            let mut query_all_renderables = scene.world.query::<&Renderable>();
163            for (id, _comps) in query_all_renderables.iter() {
164                self.command_buffer.remove_one::<ShadowMapDirty>(id);
165            }
166        }
167
168        self.command_buffer.run_on(&mut scene.world);
169    }
170
171    fn check_shadow_maps_dirty(&self, scene: &Scene) -> bool {
172        let mut query_for_shadow = scene
173            .world
174            .query::<(Option<&ShadowMapDirty>, Changed<VertsGPU>, Changed<ModelMatrix>)>()
175            .with::<&Renderable>();
176
177        //check if this light has had any changes in the scene and therefore we might
178        // need to update the shadow map. we do this check first because if the
179        // scene has not changed we don't even need to start a encoder or a render pass
180        let mut shadow_map_requires_update = false;
181        //meshes
182        for (_entity_mesh, (shadow_map_opt, changed_verts, changed_model_matrix)) in query_for_shadow.iter() {
183            shadow_map_requires_update = shadow_map_requires_update | changed_verts | changed_model_matrix | shadow_map_opt.is_some();
184        }
185
186        //check if the light shadow casting component has changed, like it was disabled
187        // or the resolution has changed
188        let mut query_for_lights = scene.world.query::<Changed<ShadowCaster>>();
189        for (_ent, changed_shadow_casting) in query_for_lights.iter() {
190            shadow_map_requires_update |= changed_shadow_casting;
191        }
192
193        //check if the renderable component has been added or changed in which case we
194        // need to update the shadows
195        let mut query_for_renderable = scene.world.query::<Changed<Renderable>>();
196        for (_entity_mesh, changed_renderable) in query_for_renderable.iter() {
197            shadow_map_requires_update |= changed_renderable;
198        }
199
200        //if ANY entity has their renderable component removed, then we also update
201        // shadows
202        if !scene.world.removed::<Renderable>().is_empty() {
203            shadow_map_requires_update = true;
204        }
205
206        //if any light has moves we also update shadows
207        let mut query_for_lights = scene.world.query::<(Changed<PosLookat>,)>().with::<&LightEmit>();
208        for (_entity_mesh, (changed_poslookat,)) in query_for_lights.iter() {
209            shadow_map_requires_update |= changed_poslookat;
210        }
211
212        shadow_map_requires_update
213    }
214
215    fn update_locals<Q: Query>(&mut self, gpu: &Gpu, query_state: &mut QueryBorrow<'_, Q>, scene: &Scene) {
216        // Update the local binding groups for the meshes we render. We do it in two
217        // passes because the binding group cannot be created and consumed in the same
218        // loop
219        for (id, _comps) in query_state.iter() {
220            let name = scene.get_comp::<&Name>(&id).unwrap().0.clone();
221
222            //upload local stuff
223            let locals = Locals::new(id, scene);
224            let offset_in_ubo = self.locals_uniform.buffer.push_cpu_chunk_aligned::<Locals>(&locals);
225
226            //chekc if we need to recreate bind group (for example when any of the textures
227            // of the meshes have changed)
228            self.locals_uniform.update_bind_group(id, gpu, &name, offset_in_ubo, scene);
229        }
230        self.locals_uniform.buffer.upload_from_cpu_chunks(gpu.queue()); //important to upload everything to gpu at the end
231    }
232}
233
234/// Keep in sync with shader
235#[repr(C)]
236#[derive(Clone, Copy, encase::ShaderType)]
237struct Locals {
238    model_matrix: nalgebra::Matrix4<f32>,
239}
240impl Locals {
241    pub fn new(entity: Entity, scene: &Scene) -> Self {
242        let model_matrix = scene.get_comp::<&ModelMatrix>(&entity).unwrap().0.to_homogeneous();
243        Locals { model_matrix }
244    }
245}
246
247struct LocalsUniform {
248    pub buffer: Buffer,
249    layout: wgpu::BindGroupLayout,
250    pub mesh2local_bind: HashMap<String, (BindGroupWrapper, u32)>,
251}
252impl LocalsUniform {
253    pub fn new(gpu: &Gpu) -> Self {
254        let size_bytes = 0x10000;
255        let usage = wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM;
256        let buffer = Buffer::new_empty(gpu.device(), usage, Some("local_buffer"), size_bytes);
257
258        let layout = Self::layout_desc().into_bind_group_layout(gpu.device());
259
260        Self {
261            buffer,
262            layout,
263            mesh2local_bind: HashMap::default(),
264        }
265    }
266
267    //keep as associated function so we can call it in the pipeline creation
268    // without and object
269    pub fn layout_desc() -> BindGroupLayoutDesc {
270        BindGroupLayoutBuilder::new()
271            .label("shadow_pass_locals_layout")
272            //Locals
273            .add_entry_uniform(
274                wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
275                true,
276                wgpu::BufferSize::new(u64::from(align(u32::try_from(std::mem::size_of::<Locals>()).unwrap(), 256))),
277            )
278            .build()
279    }
280
281    pub fn update_bind_group(
282        &mut self,
283        _entity: Entity,
284        gpu: &Gpu,
285        // offset: &u32,
286        mesh_name: &str,
287        offset_in_ubo: u32,
288        _scene: &Scene,
289    ) {
290        let entries = BindGroupBuilder::new().add_entry_buf_chunk::<Locals>(&self.buffer.buffer).build_entries();
291
292        //if there is no entry for the bind group or if the current one is stale, we
293        // recreate it
294        if !self.mesh2local_bind.contains_key(mesh_name) || self.mesh2local_bind[mesh_name].0.is_stale(&entries) {
295            debug!("shadowmap_local_bg_recreating");
296            let bg = BindGroupDesc::new("shadow_local_bg", entries).into_bind_group_wrapper(gpu.device(), &self.layout);
297            let bg_and_offset = (bg, offset_in_ubo);
298            self.mesh2local_bind.insert(mesh_name.to_string(), bg_and_offset);
299        }
300
301        //sometimes just the offset of the bind group changes so we also make sure to
302        // update this.
303        self.mesh2local_bind.entry(mesh_name.to_string()).and_modify(|r| r.1 = offset_in_ubo);
304    }
305}
306
307/// Keep in sync with shader
308#[repr(C)]
309#[derive(Clone, Copy, encase::ShaderType)]
310struct IteratorLight {
311    light_idx: u32,
312    //wasm needs padding to 16 bytes https://github.com/gfx-rs/wgpu/issues/2932
313    pad_0: u32,
314    pad_1: u32,
315    pad_2: u32,
316}
317impl IteratorLight {
318    pub fn new(light_idx: u32) -> Self {
319        IteratorLight {
320            light_idx,
321            pad_0: 0,
322            pad_1: 0,
323            pad_2: 0,
324        }
325    }
326}
327
328struct IteratorLightUniform {
329    pub buffer: Buffer,
330    pub bind_group: BindGroupWrapper,
331}
332impl IteratorLightUniform {
333    pub fn new(gpu: &Gpu) -> Self {
334        let size_bytes = align_usz(std::mem::size_of::<IteratorLight>(), 256);
335        let usage = wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM;
336        let buffer = Buffer::new_empty(gpu.device(), usage, Some("iterator_light_buffer"), size_bytes);
337
338        let layout = Self::layout_desc().into_bind_group_layout(gpu.device());
339
340        //can build it only once and leave it like this because we won't reallocate the
341        // buffer
342        let bind_group = BindGroupBuilder::new().add_entry_buf(&buffer.buffer).build(gpu.device(), &layout);
343
344        Self {
345            buffer,
346            // layout,
347            bind_group,
348        }
349    }
350
351    pub fn set(&mut self, gpu: &Gpu, idx: u32) {
352        let iterator = IteratorLight::new(idx);
353        self.buffer.reset_chunks_offset();
354        self.buffer.push_cpu_chunk_packed(&iterator);
355        self.buffer.upload_from_cpu_chunks(gpu.queue());
356    }
357
358    //keep as associated function so we can call it in the pipeline creation
359    // without and object
360    pub fn layout_desc() -> BindGroupLayoutDesc {
361        BindGroupLayoutBuilder::new()
362            .label("shadow_pass_iterator_layout")
363            //InteratorLight
364            .add_entry_uniform(
365                wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
366                false,
367                wgpu::BufferSize::new(u64::from(align(u32::try_from(std::mem::size_of::<IteratorLight>()).unwrap(), 256))),
368            )
369            .build()
370    }
371}