gloss_renderer/forward_renderer/render_passes/
mesh_pipeline.rs

1use std::collections::HashMap;
2
3use crate::{
4    components::{
5        ColorsGPU, DiffuseTex, EnvironmentMapGpu, FacesGPU, ModelMatrix, Name, NormalTex, NormalsGPU, Renderable, RoughnessTex, ShadowCaster,
6        ShadowMap, TangentsGPU, UVsGPU, VertsGPU, VisMesh,
7    },
8    config::RenderConfig,
9    forward_renderer::{bind_group_collection::BindGroupCollection, locals::LocalEntData},
10    light::Light,
11    scene::Scene,
12    selector::Selector,
13};
14use easy_wgpu::{
15    bind_group::{BindGroupBuilder, BindGroupDesc, BindGroupWrapper},
16    bind_group_layout::{BindGroupLayoutBuilder, BindGroupLayoutDesc},
17    buffer::Buffer,
18};
19// use gloss_utils::log;
20
21use easy_wgpu::{
22    gpu::Gpu,
23    texture::{TexParams, Texture},
24    utils::create_empty_group,
25};
26use gloss_hecs::Entity;
27use log::debug;
28
29use super::{pipeline_runner::PipelineRunner, upload_pass::PerFrameUniforms};
30
31use easy_wgpu::pipeline::RenderPipelineDescBuilder;
32
33use super::upload_pass::MAX_NUM_SHADOWS;
34use encase;
35
36use gloss_utils::numerical::align;
37
38//shaders
39#[include_wgsl_oil::include_wgsl_oil("../../../shaders/gbuffer_mesh_vert.wgsl")]
40mod vert_shader_code {}
41#[allow(clippy::approx_constant)]
42#[include_wgsl_oil::include_wgsl_oil("../../../shaders/gbuffer_mesh_frag.wgsl")]
43mod frag_shader_code {}
44
45/// Render all the meshes from the scene to the `GBuffer`
46pub struct MeshPipeline {
47    render_pipeline: wgpu::RenderPipeline,
48    _empty_group: wgpu::BindGroup,
49    locals_uniform: Buffer, // a uniform buffer that we suballocate for the locals of every mesh
50    locals_bind_groups: LocalsBindGroups,
51    /// layout of the input to the mesh pass. Usually contains gbuffer textures,
52    /// shadow maps, etc.
53    input_layout: wgpu::BindGroupLayout,
54    input_bind_group: Option<BindGroupWrapper>,
55    //misc
56    /// stores some entities that should be local to this pass and don't need to
57    /// be stored in the main scene
58    local_scene: Scene,
59    /// Light that is used as a dummy. Serves to bind to the [``input_layout``]
60    /// and [``input_bind_group``] and satisfy the layout needs even if we don't
61    /// actually use this light.
62    passthrough_light: Light,
63}
64
65impl MeshPipeline {
66    /// # Panics
67    /// Will panic if the gbuffer does not have the correct textures that are
68    /// needed for the pipeline creation
69    pub fn new(gpu: &Gpu, params: &RenderConfig, color_target_format: wgpu::TextureFormat, depth_target_format: wgpu::TextureFormat) -> Self {
70        //wasm likes everything to be 16 bytes aligned
71        const_assert!(std::mem::size_of::<Locals>() % 16 == 0);
72
73        let input_layout_desc = Self::input_layout_desc();
74        let input_layout = input_layout_desc.clone().into_bind_group_layout(gpu.device());
75
76        //render pipeline
77        let render_pipeline = RenderPipelineDescBuilder::new()
78            .label("mesh_pipeline")
79            //Code has to be sparated between vert and frag because we use derivatives with dpdx in frag shader and that fails to compiles on wasm when in the same file as the vert shader: https://github.com/gfx-rs/wgpu/issues/4368
80            .shader_code_vert(vert_shader_code::SOURCE)
81            .shader_code_frag(frag_shader_code::SOURCE)
82            .shader_label("mesh_shader")
83            .add_bind_group_layout_desc(PerFrameUniforms::build_layout_desc())
84            .add_bind_group_layout_desc(input_layout_desc)
85            .add_bind_group_layout_desc(LocalsBindGroups::build_layout_desc())
86            .add_vertex_buffer_layout(VertsGPU::vertex_buffer_layout::<0>())
87            .add_vertex_buffer_layout(UVsGPU::vertex_buffer_layout::<1>())
88            .add_vertex_buffer_layout(NormalsGPU::vertex_buffer_layout::<2>())
89            .add_vertex_buffer_layout(TangentsGPU::vertex_buffer_layout::<3>())
90            .add_vertex_buffer_layout(ColorsGPU::vertex_buffer_layout::<4>())
91            .add_render_target(wgpu::ColorTargetState {
92                format: color_target_format,
93                blend: None,
94                write_mask: wgpu::ColorWrites::ALL,
95            })
96            .depth_state(Some(wgpu::DepthStencilState {
97                format: depth_target_format,
98                depth_write_enabled: true,
99                depth_compare: wgpu::CompareFunction::Greater,
100                stencil: wgpu::StencilState {
101                    front: wgpu::StencilFaceState {
102                        compare: wgpu::CompareFunction::Always,
103                        fail_op: wgpu::StencilOperation::Replace,
104                        depth_fail_op: wgpu::StencilOperation::Replace,
105                        pass_op: wgpu::StencilOperation::Replace,
106                    },
107                    back: wgpu::StencilFaceState {
108                        compare: wgpu::CompareFunction::Always,
109                        fail_op: wgpu::StencilOperation::Replace,
110                        depth_fail_op: wgpu::StencilOperation::Replace,
111                        pass_op: wgpu::StencilOperation::Replace,
112                    },
113                    read_mask: 0x00,
114                    write_mask: 0xFF,
115                },
116                bias: wgpu::DepthBiasState::default(),
117            }))
118            .multisample(wgpu::MultisampleState {
119                count: params.msaa_nr_samples,
120                ..Default::default()
121            })
122            .build_pipeline(gpu.device());
123
124        let empty_group = create_empty_group(gpu.device());
125
126        let size_bytes = 0x10000;
127        let usage = wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM;
128        let locals_uniform = Buffer::new_empty(gpu.device(), usage, Some("local_buffer"), size_bytes);
129
130        let locals_bind_groups = LocalsBindGroups::new(gpu);
131
132        //we want to add a dummy light and we do it in a local world so we don't have
133        // to depend on the main scene
134        let mut local_scene = Scene::new();
135        // let passthrough_tex = Texture::create_default_texture(gpu.device(),
136        // gpu.queue());
137        let passthrough_tex = Texture::new(
138            gpu.device(),
139            4,
140            4,
141            wgpu::TextureFormat::Depth32Float,
142            wgpu::TextureUsages::TEXTURE_BINDING,
143            TexParams::default(),
144        );
145        // let passthrough_tex2 = Texture::create_default_texture(gpu.device(),
146        // gpu.queue());
147        let passthrough_light = Light::new("compose_pass_passthrough_light", &mut local_scene);
148        let _ = local_scene.world.insert_one(
149            passthrough_light.entity,
150            ShadowMap {
151                tex_depth: passthrough_tex,
152                // tex_depth_moments: passthrough_tex2,
153            },
154        );
155
156        Self {
157            render_pipeline,
158            _empty_group: empty_group,
159            locals_uniform,
160            locals_bind_groups,
161            input_layout,
162            input_bind_group: None,
163            //misc
164            local_scene,
165            passthrough_light,
166        }
167    }
168}
169impl PipelineRunner for MeshPipeline {
170    type QueryItems<'a> = (
171        &'a VertsGPU,
172        &'a FacesGPU,
173        &'a UVsGPU,
174        &'a NormalsGPU,
175        &'a TangentsGPU,
176        &'a ColorsGPU,
177        &'a DiffuseTex,
178        &'a NormalTex,
179        &'a RoughnessTex,
180        &'a VisMesh,
181        &'a Name,
182    );
183    type QueryState<'a> = gloss_hecs::QueryBorrow<'a, gloss_hecs::With<Self::QueryItems<'a>, &'a Renderable>>;
184
185    fn query_state(scene: &Scene) -> Self::QueryState<'_> {
186        scene.world.query::<Self::QueryItems<'_>>().with::<&Renderable>()
187    }
188
189    fn prepare<'a>(&mut self, gpu: &Gpu, per_frame_uniforms: &PerFrameUniforms, scene: &'a Scene) -> Self::QueryState<'a> {
190        self.begin_pass();
191
192        self.update_locals(gpu, scene);
193
194        //update the bind group in case the input_texture or the shadowmaps changed
195        self.update_input_bind_group(gpu, scene, per_frame_uniforms);
196
197        Self::query_state(scene)
198    }
199
200    /// # Panics
201    /// Will panic if the input bind groups are not created
202    #[allow(clippy::too_many_lines)]
203    fn run<'r>(
204        &'r mut self,
205        render_pass: &mut wgpu::RenderPass<'r>,
206        per_frame_uniforms: &'r PerFrameUniforms,
207        _render_params: &RenderConfig,
208        query_state: &'r mut Self::QueryState<'_>,
209        scene: &Scene,
210    ) {
211        //completely skip this if there are no entities to draw
212        if query_state.iter().count() == 0 {
213            return;
214        }
215        let selector = scene.get_resource::<&Selector>();
216        render_pass.set_pipeline(&self.render_pipeline);
217
218        //global binding
219        render_pass.set_bind_group(0, &per_frame_uniforms.bind_group, &[]);
220        //input binding
221        render_pass.set_bind_group(1, self.input_bind_group.as_ref().unwrap().bg(), &[]);
222
223        /* ---------------------------------- NOTE ---------------------------------- */
224        // When we set stencil reference, all further draw calls will write to the stencil buffer.
225        // We do not want that happening since we want to draw to the stencil buffer only using the selected ent
226        // So we do this below for the selected entity after the loop. The first loop draws the unselected entities,
227        //all with stencil reference 0.
228        // The part after draws the selected entity with stencil reference 1, used by the outline pass
229        /* -------------------------------------------------------------------------- */
230
231        render_pass.set_stencil_reference(0);
232        for (_id, (verts, faces, uvs, normals, tangents, colors, _diffuse_tex, _normal_tex, _roughness_tex, vis_mesh, name)) in query_state.iter() {
233            if !vis_mesh.show_mesh {
234                continue;
235            }
236
237            // Skip in the loop and do after
238            if let Ok(ref current_selector) = selector {
239                if name.0 == current_selector.current_selected {
240                    continue;
241                }
242            }
243
244            //local bindings
245            let (local_bg, offset) = &self.locals_bind_groups.mesh2local_bind[&name.0.clone()];
246            render_pass.set_bind_group(2, local_bg.bg(), &[*offset]);
247            render_pass.set_vertex_buffer(0, verts.buf.slice(..));
248            render_pass.set_vertex_buffer(1, uvs.buf.slice(..));
249            render_pass.set_vertex_buffer(2, normals.buf.slice(..));
250            render_pass.set_vertex_buffer(3, tangents.buf.slice(..));
251            render_pass.set_vertex_buffer(4, colors.buf.slice(..));
252            render_pass.set_index_buffer(faces.buf.slice(..), wgpu::IndexFormat::Uint32);
253            render_pass.draw_indexed(0..faces.nr_triangles * 3, 0, 0..1);
254        }
255
256        for (_id, (verts, faces, uvs, normals, tangents, colors, _diffuse_tex, _normal_tex, _roughness_tex, vis_mesh, name)) in query_state.iter() {
257            if let Ok(ref current_selector) = selector {
258                if name.0 == current_selector.current_selected {
259                    if !vis_mesh.show_mesh {
260                        return;
261                    }
262
263                    render_pass.set_stencil_reference(1);
264
265                    let (local_bg, offset) = &self.locals_bind_groups.mesh2local_bind[&current_selector.current_selected.clone()];
266                    render_pass.set_bind_group(2, local_bg.bg(), &[*offset]);
267                    render_pass.set_vertex_buffer(0, verts.buf.slice(..));
268                    render_pass.set_vertex_buffer(1, uvs.buf.slice(..));
269                    render_pass.set_vertex_buffer(2, normals.buf.slice(..));
270                    render_pass.set_vertex_buffer(3, tangents.buf.slice(..));
271                    render_pass.set_vertex_buffer(4, colors.buf.slice(..));
272                    render_pass.set_index_buffer(faces.buf.slice(..), wgpu::IndexFormat::Uint32);
273                    render_pass.draw_indexed(0..faces.nr_triangles * 3, 0, 0..1);
274                }
275            }
276        }
277
278        // This is an alternative way to do the same
279        // if let Ok(ref current_selector) = selector {
280        //     if let Some(selected_entity) = scene.get_entity_with_name(&current_selector.current_selected) {
281        //         let vis_mesh = scene.get_comp::<&VisMesh>(&selected_entity).unwrap();
282        //         // The selected avatar might delete its renderable comp when its outside its timeline, we skip this in that case
283        //         if !vis_mesh.show_mesh {
284        //             return;
285        //         }
286
287        //         render_pass.set_stencil_reference(1);
288
289        //         let (local_bg, offset) = &self.locals_bind_groups.mesh2local_bind[&current_selector.current_selected.clone()];
290        //         let verts = scene.get_comp::<&VertsGPU>(&selected_entity).unwrap();
291        //         let faces = scene.get_comp::<&FacesGPU>(&selected_entity).unwrap();
292        //         let uvs = scene.get_comp::<&UVsGPU>(&selected_entity).unwrap();
293        //         let normals = scene.get_comp::<&NormalsGPU>(&selected_entity).unwrap();
294        //         let tangents = scene.get_comp::<&TangentsGPU>(&selected_entity).unwrap();
295        //         let colors = scene.get_comp::<&ColorsGPU>(&selected_entity).unwrap();
296
297        //         render_pass.set_bind_group(2, local_bg.bg(), &[*offset]);
298        //         render_pass.set_vertex_buffer(0, verts.buf.slice(..));
299        //         render_pass.set_vertex_buffer(1, uvs.buf.slice(..));
300        //         render_pass.set_vertex_buffer(2, normals.buf.slice(..));
301        //         render_pass.set_vertex_buffer(3, tangents.buf.slice(..));
302        //         render_pass.set_vertex_buffer(4, colors.buf.slice(..));
303        //         render_pass.set_index_buffer(faces.buf.slice(..), wgpu::IndexFormat::Uint32);
304        //         render_pass.draw_indexed(0..faces.nr_triangles * 3, 0, 0..1);
305        //     }
306        // }
307    }
308
309    fn begin_pass(&mut self) {}
310
311    fn input_layout_desc() -> BindGroupLayoutDesc {
312        BindGroupLayoutBuilder::new()
313            .label("compose_input_layout")
314            // diffuse cubemap
315            .add_entry_cubemap(wgpu::ShaderStages::FRAGMENT, wgpu::TextureSampleType::Float { filterable: true })
316            //specular cubemap
317            .add_entry_cubemap(wgpu::ShaderStages::FRAGMENT, wgpu::TextureSampleType::Float { filterable: true })
318            //shadow_maps
319            .add_entries_tex(
320                wgpu::ShaderStages::FRAGMENT,
321                wgpu::TextureSampleType::Depth, /* float textures cannot be linearly filtered on webgpu :( But you can use a sampler with
322                                                 * comparison and it does a hardware 2x2 pcf */
323                // wgpu::TextureSampleType::Float { filterable: false }, //float textures cannot be linearly filtered on webgpu :(
324                MAX_NUM_SHADOWS,
325            )
326            .build()
327    }
328
329    fn update_input_bind_group(&mut self, gpu: &Gpu, scene: &Scene, per_frame_uniforms: &PerFrameUniforms) {
330        //get envmap
331        let env_map = scene.get_resource::<&EnvironmentMapGpu>().unwrap();
332
333        let mut shadow_maps = Vec::new();
334
335        //attempt adding shadow maps for all the lights, first the real lights that are
336        // actually in the scene and afterwards we will with dummy values
337        for i in 0..MAX_NUM_SHADOWS {
338            let is_within_valid_lights: bool = i < per_frame_uniforms.idx_ubo2light.len();
339            if is_within_valid_lights && scene.world.has::<ShadowCaster>(per_frame_uniforms.idx_ubo2light[i]).unwrap() {
340                let entity = per_frame_uniforms.idx_ubo2light[i];
341                let shadow = scene
342                    .get_comp::<&ShadowMap>(&entity)
343                    .expect("The lights who have a ShadowCaster should also have ShadowMap at this point.");
344                shadow_maps.push(shadow);
345            } else {
346                //dummy passthough shadowmap
347                let shadow: gloss_hecs::Ref<'_, ShadowMap> = self
348                    .local_scene
349                    .get_comp::<&ShadowMap>(&self.passthrough_light.entity)
350                    .expect("Dummy light should have ShadowMap");
351                shadow_maps.push(shadow);
352            }
353        }
354
355        let entries = BindGroupBuilder::new()
356            .add_entry_tex(&env_map.diffuse_tex)
357            .add_entry_tex(&env_map.specular_tex)
358            .add_entry_tex(&shadow_maps[0].tex_depth)
359            .add_entry_tex(&shadow_maps[1].tex_depth)
360            .add_entry_tex(&shadow_maps[2].tex_depth)
361            .build_entries();
362        // let stale = self.input_bind_group.as_ref().map_or(true, |b| b.is_stale(&entries)); //returns true if the bg has not been created or if stale
363        let stale = self.input_bind_group.as_ref().is_none_or(|b| b.is_stale(&entries));
364        if stale {
365            debug!("compose input bind group is stale, recreating");
366            self.input_bind_group = Some(BindGroupDesc::new("compose_input_bg", entries).into_bind_group_wrapper(gpu.device(), &self.input_layout));
367        }
368    }
369
370    /// update the local information that need to be sent to the gpu for each
371    /// mesh like te model matrix
372    fn update_locals(&mut self, gpu: &Gpu, scene: &Scene) {
373        Self::update_locals_inner::<Locals, _>(
374            gpu,
375            scene,
376            &mut self.locals_uniform,
377            &mut self.locals_bind_groups,
378            &mut Self::query_state(scene),
379        );
380    }
381}
382
383/// Keep in sync with shader `gbuffer_mesh.wgsl`
384#[repr(C)]
385#[derive(Clone, Copy, encase::ShaderType)]
386struct Locals {
387    model_matrix: nalgebra::Matrix4<f32>,
388    color_type: i32,
389    solid_color: nalgebra::Vector4<f32>,
390    metalness: f32,
391    perceptual_roughness: f32,
392    roughness_black_lvl: f32,
393    uv_scale: f32,
394    is_floor: u32,
395    //wasm needs padding to 16 bytes https://github.com/gfx-rs/wgpu/issues/2932
396    // pad_b: f32,
397    pad_c: f32,
398    pad_d: f32,
399}
400impl LocalEntData for Locals {
401    fn new(entity: Entity, scene: &Scene) -> Self {
402        let model_matrix = scene.get_comp::<&ModelMatrix>(&entity).unwrap().0.to_homogeneous();
403        let vis_mesh = scene.get_comp::<&VisMesh>(&entity).unwrap();
404        let color_type = vis_mesh.color_type as i32;
405        let is_floor = if let Some(floor) = scene.get_floor() {
406            floor.entity == entity
407        } else {
408            false
409        };
410        let is_floor = u32::from(is_floor);
411        Locals {
412            model_matrix,
413            color_type,
414            solid_color: vis_mesh.solid_color,
415            metalness: vis_mesh.metalness,
416            perceptual_roughness: vis_mesh.perceptual_roughness,
417            roughness_black_lvl: vis_mesh.roughness_black_lvl,
418            uv_scale: vis_mesh.uv_scale,
419            is_floor,
420            pad_c: 0.0,
421            pad_d: 0.0,
422        }
423    }
424}
425
426struct LocalsBindGroups {
427    layout: wgpu::BindGroupLayout,
428    pub mesh2local_bind: HashMap<String, (BindGroupWrapper, u32)>,
429}
430impl BindGroupCollection for LocalsBindGroups {
431    fn new(gpu: &Gpu) -> Self {
432        Self {
433            layout: Self::build_layout_desc().into_bind_group_layout(gpu.device()),
434            mesh2local_bind: HashMap::default(),
435        }
436    }
437
438    //keep as associated function so we can call it in the pipeline creation
439    // without and object
440    fn build_layout_desc() -> BindGroupLayoutDesc {
441        BindGroupLayoutBuilder::new()
442            .label("gbuffer_pass_locals_layout")
443            //locals
444            .add_entry_uniform(
445                wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
446                true,
447                wgpu::BufferSize::new(u64::from(align(u32::try_from(std::mem::size_of::<Locals>()).unwrap(), 256))),
448            )
449            //diffuse tex
450            .add_entry_tex(wgpu::ShaderStages::FRAGMENT, wgpu::TextureSampleType::Float { filterable: true })
451            //normal tex
452            .add_entry_tex(wgpu::ShaderStages::FRAGMENT, wgpu::TextureSampleType::Float { filterable: true })
453            //roughness tex
454            .add_entry_tex(wgpu::ShaderStages::FRAGMENT, wgpu::TextureSampleType::Float { filterable: true })
455            .build()
456    }
457
458    fn update_bind_group(&mut self, entity: Entity, gpu: &Gpu, mesh_name: &str, ubo: &Buffer, offset_in_ubo: u32, scene: &Scene) {
459        //extract textures for this entity
460        let diffuse_tex = &scene.get_comp::<&DiffuseTex>(&entity).unwrap().0;
461        let normal_tex = &scene.get_comp::<&NormalTex>(&entity).unwrap().0;
462        let roughness_tex = &scene.get_comp::<&RoughnessTex>(&entity).unwrap().0;
463
464        let entries = BindGroupBuilder::new()
465            .add_entry_buf_chunk::<Locals>(&ubo.buffer)
466            .add_entry_tex(diffuse_tex)
467            .add_entry_tex(normal_tex)
468            .add_entry_tex(roughness_tex)
469            .build_entries();
470
471        self.update_if_stale(mesh_name, entries, offset_in_ubo, gpu);
472    }
473
474    fn get_layout(&self) -> &wgpu::BindGroupLayout {
475        &self.layout
476    }
477    fn get_mut_entity2binds(&mut self) -> &mut HashMap<String, (BindGroupWrapper, u32)> {
478        &mut self.mesh2local_bind
479    }
480}