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 let Some(current_selected) = &current_selector.current_selected {
240                    if name.0 == *current_selected {
241                        continue;
242                    }
243                }
244            }
245
246            //local bindings
247            let (local_bg, offset) = &self.locals_bind_groups.mesh2local_bind[&name.0.clone()];
248            render_pass.set_bind_group(2, local_bg.bg(), &[*offset]);
249            render_pass.set_vertex_buffer(0, verts.buf.slice(..));
250            render_pass.set_vertex_buffer(1, uvs.buf.slice(..));
251            render_pass.set_vertex_buffer(2, normals.buf.slice(..));
252            render_pass.set_vertex_buffer(3, tangents.buf.slice(..));
253            render_pass.set_vertex_buffer(4, colors.buf.slice(..));
254            render_pass.set_index_buffer(faces.buf.slice(..), wgpu::IndexFormat::Uint32);
255            render_pass.draw_indexed(0..faces.nr_triangles * 3, 0, 0..1);
256        }
257
258        for (_id, (verts, faces, uvs, normals, tangents, colors, _diffuse_tex, _normal_tex, _roughness_tex, vis_mesh, name)) in query_state.iter() {
259            if let Ok(ref current_selector) = selector {
260                if let Some(current_selected) = &current_selector.current_selected {
261                    if name.0 == *current_selected {
262                        if !vis_mesh.show_mesh {
263                            return;
264                        }
265
266                        render_pass.set_stencil_reference(1);
267
268                        let (local_bg, offset) = &self.locals_bind_groups.mesh2local_bind[&current_selected.clone()];
269                        render_pass.set_bind_group(2, local_bg.bg(), &[*offset]);
270                        render_pass.set_vertex_buffer(0, verts.buf.slice(..));
271                        render_pass.set_vertex_buffer(1, uvs.buf.slice(..));
272                        render_pass.set_vertex_buffer(2, normals.buf.slice(..));
273                        render_pass.set_vertex_buffer(3, tangents.buf.slice(..));
274                        render_pass.set_vertex_buffer(4, colors.buf.slice(..));
275                        render_pass.set_index_buffer(faces.buf.slice(..), wgpu::IndexFormat::Uint32);
276                        render_pass.draw_indexed(0..faces.nr_triangles * 3, 0, 0..1);
277                    }
278                }
279            }
280        }
281
282        // This is an alternative way to do the same
283        // if let Ok(ref current_selector) = selector {
284        //     if let Some(selected_entity) = scene.get_entity_with_name(&current_selector.current_selected) {
285        //         let vis_mesh = scene.get_comp::<&VisMesh>(&selected_entity).unwrap();
286        //         // The selected avatar might delete its renderable comp when its outside its timeline, we skip this in that case
287        //         if !vis_mesh.show_mesh {
288        //             return;
289        //         }
290
291        //         render_pass.set_stencil_reference(1);
292
293        //         let (local_bg, offset) = &self.locals_bind_groups.mesh2local_bind[&current_selector.current_selected.clone()];
294        //         let verts = scene.get_comp::<&VertsGPU>(&selected_entity).unwrap();
295        //         let faces = scene.get_comp::<&FacesGPU>(&selected_entity).unwrap();
296        //         let uvs = scene.get_comp::<&UVsGPU>(&selected_entity).unwrap();
297        //         let normals = scene.get_comp::<&NormalsGPU>(&selected_entity).unwrap();
298        //         let tangents = scene.get_comp::<&TangentsGPU>(&selected_entity).unwrap();
299        //         let colors = scene.get_comp::<&ColorsGPU>(&selected_entity).unwrap();
300
301        //         render_pass.set_bind_group(2, local_bg.bg(), &[*offset]);
302        //         render_pass.set_vertex_buffer(0, verts.buf.slice(..));
303        //         render_pass.set_vertex_buffer(1, uvs.buf.slice(..));
304        //         render_pass.set_vertex_buffer(2, normals.buf.slice(..));
305        //         render_pass.set_vertex_buffer(3, tangents.buf.slice(..));
306        //         render_pass.set_vertex_buffer(4, colors.buf.slice(..));
307        //         render_pass.set_index_buffer(faces.buf.slice(..), wgpu::IndexFormat::Uint32);
308        //         render_pass.draw_indexed(0..faces.nr_triangles * 3, 0, 0..1);
309        //     }
310        // }
311    }
312
313    fn begin_pass(&mut self) {}
314
315    fn input_layout_desc() -> BindGroupLayoutDesc {
316        BindGroupLayoutBuilder::new()
317            .label("compose_input_layout")
318            // diffuse cubemap
319            .add_entry_cubemap(wgpu::ShaderStages::FRAGMENT, wgpu::TextureSampleType::Float { filterable: true })
320            //specular cubemap
321            .add_entry_cubemap(wgpu::ShaderStages::FRAGMENT, wgpu::TextureSampleType::Float { filterable: true })
322            //shadow_maps
323            .add_entries_tex(
324                wgpu::ShaderStages::FRAGMENT,
325                wgpu::TextureSampleType::Depth, /* float textures cannot be linearly filtered on webgpu :( But you can use a sampler with
326                                                 * comparison and it does a hardware 2x2 pcf */
327                // wgpu::TextureSampleType::Float { filterable: false }, //float textures cannot be linearly filtered on webgpu :(
328                MAX_NUM_SHADOWS,
329            )
330            .build()
331    }
332
333    fn update_input_bind_group(&mut self, gpu: &Gpu, scene: &Scene, per_frame_uniforms: &PerFrameUniforms) {
334        //get envmap
335        let env_map = scene.get_resource::<&EnvironmentMapGpu>().unwrap();
336
337        let mut shadow_maps = Vec::new();
338
339        //attempt adding shadow maps for all the lights, first the real lights that are
340        // actually in the scene and afterwards we will with dummy values
341        for i in 0..MAX_NUM_SHADOWS {
342            let is_within_valid_lights: bool = i < per_frame_uniforms.idx_ubo2light.len();
343            if is_within_valid_lights && scene.world.has::<ShadowCaster>(per_frame_uniforms.idx_ubo2light[i]).unwrap() {
344                let entity = per_frame_uniforms.idx_ubo2light[i];
345                let shadow = scene
346                    .get_comp::<&ShadowMap>(&entity)
347                    .expect("The lights who have a ShadowCaster should also have ShadowMap at this point.");
348                shadow_maps.push(shadow);
349            } else {
350                //dummy passthough shadowmap
351                let shadow: gloss_hecs::Ref<'_, ShadowMap> = self
352                    .local_scene
353                    .get_comp::<&ShadowMap>(&self.passthrough_light.entity)
354                    .expect("Dummy light should have ShadowMap");
355                shadow_maps.push(shadow);
356            }
357        }
358
359        let entries = BindGroupBuilder::new()
360            .add_entry_tex(&env_map.diffuse_tex)
361            .add_entry_tex(&env_map.specular_tex)
362            .add_entry_tex(&shadow_maps[0].tex_depth)
363            .add_entry_tex(&shadow_maps[1].tex_depth)
364            .add_entry_tex(&shadow_maps[2].tex_depth)
365            .build_entries();
366        // 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
367        let stale = self.input_bind_group.as_ref().is_none_or(|b| b.is_stale(&entries));
368        if stale {
369            debug!("compose input bind group is stale, recreating");
370            self.input_bind_group = Some(BindGroupDesc::new("compose_input_bg", entries).into_bind_group_wrapper(gpu.device(), &self.input_layout));
371        }
372    }
373
374    /// update the local information that need to be sent to the gpu for each
375    /// mesh like te model matrix
376    fn update_locals(&mut self, gpu: &Gpu, scene: &Scene) {
377        Self::update_locals_inner::<Locals, _>(
378            gpu,
379            scene,
380            &mut self.locals_uniform,
381            &mut self.locals_bind_groups,
382            &mut Self::query_state(scene),
383        );
384    }
385}
386
387/// Keep in sync with shader `gbuffer_mesh.wgsl`
388#[repr(C)]
389#[derive(Clone, Copy, encase::ShaderType)]
390struct Locals {
391    model_matrix: nalgebra::Matrix4<f32>,
392    color_type: i32,
393    solid_color: nalgebra::Vector4<f32>,
394    metalness: f32,
395    perceptual_roughness: f32,
396    roughness_black_lvl: f32,
397    uv_scale: f32,
398    is_floor: u32,
399    //wasm needs padding to 16 bytes https://github.com/gfx-rs/wgpu/issues/2932
400    // pad_b: f32,
401    pad_c: f32,
402    pad_d: f32,
403}
404impl LocalEntData for Locals {
405    fn new(entity: Entity, scene: &Scene) -> Self {
406        let model_matrix = scene.get_comp::<&ModelMatrix>(&entity).unwrap().0.to_homogeneous();
407        let vis_mesh = scene.get_comp::<&VisMesh>(&entity).unwrap();
408        let color_type = vis_mesh.color_type as i32;
409        let is_floor = if let Some(floor) = scene.get_floor() {
410            floor.entity == entity
411        } else {
412            false
413        };
414        let is_floor = u32::from(is_floor);
415        Locals {
416            model_matrix,
417            color_type,
418            solid_color: vis_mesh.solid_color,
419            metalness: vis_mesh.metalness,
420            perceptual_roughness: vis_mesh.perceptual_roughness,
421            roughness_black_lvl: vis_mesh.roughness_black_lvl,
422            uv_scale: vis_mesh.uv_scale,
423            is_floor,
424            pad_c: 0.0,
425            pad_d: 0.0,
426        }
427    }
428}
429
430struct LocalsBindGroups {
431    layout: wgpu::BindGroupLayout,
432    pub mesh2local_bind: HashMap<String, (BindGroupWrapper, u32)>,
433}
434impl BindGroupCollection for LocalsBindGroups {
435    fn new(gpu: &Gpu) -> Self {
436        Self {
437            layout: Self::build_layout_desc().into_bind_group_layout(gpu.device()),
438            mesh2local_bind: HashMap::default(),
439        }
440    }
441
442    //keep as associated function so we can call it in the pipeline creation
443    // without and object
444    fn build_layout_desc() -> BindGroupLayoutDesc {
445        BindGroupLayoutBuilder::new()
446            .label("gbuffer_pass_locals_layout")
447            //locals
448            .add_entry_uniform(
449                wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
450                true,
451                wgpu::BufferSize::new(u64::from(align(u32::try_from(std::mem::size_of::<Locals>()).unwrap(), 256))),
452            )
453            //diffuse tex
454            .add_entry_tex(wgpu::ShaderStages::FRAGMENT, wgpu::TextureSampleType::Float { filterable: true })
455            //normal tex
456            .add_entry_tex(wgpu::ShaderStages::FRAGMENT, wgpu::TextureSampleType::Float { filterable: true })
457            //roughness tex
458            .add_entry_tex(wgpu::ShaderStages::FRAGMENT, wgpu::TextureSampleType::Float { filterable: true })
459            .build()
460    }
461
462    fn update_bind_group(&mut self, entity: Entity, gpu: &Gpu, mesh_name: &str, ubo: &Buffer, offset_in_ubo: u32, scene: &Scene) {
463        //extract textures for this entity
464        let diffuse_tex = &scene.get_comp::<&DiffuseTex>(&entity).unwrap().0;
465        let normal_tex = &scene.get_comp::<&NormalTex>(&entity).unwrap().0;
466        let roughness_tex = &scene.get_comp::<&RoughnessTex>(&entity).unwrap().0;
467
468        let entries = BindGroupBuilder::new()
469            .add_entry_buf_chunk::<Locals>(&ubo.buffer)
470            .add_entry_tex(diffuse_tex)
471            .add_entry_tex(normal_tex)
472            .add_entry_tex(roughness_tex)
473            .build_entries();
474
475        self.update_if_stale(mesh_name, entries, offset_in_ubo, gpu);
476    }
477
478    fn get_layout(&self) -> &wgpu::BindGroupLayout {
479        &self.layout
480    }
481    fn get_mut_entity2binds(&mut self) -> &mut HashMap<String, (BindGroupWrapper, u32)> {
482        &mut self.mesh2local_bind
483    }
484}