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