gloss_renderer/forward_renderer/render_passes/
outline_pass.rs

1use std::collections::HashMap;
2
3use crate::{
4    components::{FacesGPU, ModelMatrix, Name, NormalsGPU, Renderable, VertsGPU, VisMesh, VisOutline},
5    config::RenderConfig,
6    forward_renderer::{bind_group_collection::BindGroupCollection, locals::LocalEntData},
7    scene::Scene,
8};
9use easy_wgpu::{
10    bind_group::{BindGroupBuilder, BindGroupWrapper},
11    bind_group_layout::{BindGroupLayoutBuilder, BindGroupLayoutDesc},
12    buffer::Buffer,
13};
14// use gloss_utils::log;
15
16use easy_wgpu::{gpu::Gpu, utils::create_empty_group};
17use gloss_hecs::Entity;
18
19use super::{pipeline_runner::PipelineRunner, upload_pass::PerFrameUniforms};
20
21use easy_wgpu::pipeline::RenderPipelineDescBuilder;
22
23use encase;
24
25use gloss_utils::numerical::align;
26
27//shaders
28#[include_wgsl_oil::include_wgsl_oil("../../../shaders/outline.wgsl")]
29mod shader_code {}
30
31/// Render all the meshes from the scene to the `GBuffer`
32pub struct OutlinePass {
33    render_pipeline: wgpu::RenderPipeline,
34    _empty_group: wgpu::BindGroup,
35    locals_uniform: Buffer,
36    locals_bind_groups: LocalsBindGroups,
37}
38
39impl OutlinePass {
40    /// # Panics
41    /// Will panic if the gbuffer does not have the correct textures that are
42    /// needed for the pipeline creation
43    pub fn new(gpu: &Gpu, params: &RenderConfig, color_target_format: wgpu::TextureFormat, depth_target_format: wgpu::TextureFormat) -> Self {
44        //wasm likes everything to be 16 bytes aligned
45        const_assert!(std::mem::size_of::<Locals>() % 16 == 0);
46
47        // Create the render pipeline for outlines
48        let render_pipeline = RenderPipelineDescBuilder::new()
49            .label("outline_pass")
50            .shader_code(shader_code::SOURCE)
51            .shader_label("outline_shader")
52            .add_bind_group_layout_desc(PerFrameUniforms::build_layout_desc())
53            .add_bind_group_layout_desc(LocalsBindGroups::build_layout_desc())
54            .add_vertex_buffer_layout(VertsGPU::vertex_buffer_layout::<0>())
55            .add_vertex_buffer_layout(NormalsGPU::vertex_buffer_layout::<1>())
56            .add_render_target(wgpu::ColorTargetState {
57                format: color_target_format,
58                blend: None,
59                write_mask: wgpu::ColorWrites::ALL,
60            })
61            .depth_state(Some(wgpu::DepthStencilState {
62                format: depth_target_format,
63                depth_write_enabled: false,
64                depth_compare: wgpu::CompareFunction::Always, // Always pass depth test
65                stencil: wgpu::StencilState {
66                    front: wgpu::StencilFaceState {
67                        compare: wgpu::CompareFunction::NotEqual, // Only draw where stencil is not 1
68                        fail_op: wgpu::StencilOperation::Keep,
69                        depth_fail_op: wgpu::StencilOperation::Keep,
70                        pass_op: wgpu::StencilOperation::Keep,
71                    },
72                    back: wgpu::StencilFaceState {
73                        compare: wgpu::CompareFunction::NotEqual, // Only draw where stencil is not 1
74                        fail_op: wgpu::StencilOperation::Keep,
75                        depth_fail_op: wgpu::StencilOperation::Keep,
76                        pass_op: wgpu::StencilOperation::Keep,
77                    },
78                    read_mask: 0xFF,
79                    write_mask: 0x00, // Don't write to stencil
80                },
81                bias: wgpu::DepthBiasState::default(),
82            }))
83            .multisample(wgpu::MultisampleState {
84                count: params.msaa_nr_samples,
85                ..Default::default()
86            })
87            .build_pipeline(gpu.device());
88
89        let empty_group = create_empty_group(gpu.device());
90
91        let size_bytes = 0x10000;
92        let usage = wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM;
93        let locals_uniform = Buffer::new_empty(gpu.device(), usage, Some("local_buffer"), size_bytes);
94
95        let locals_bind_groups = LocalsBindGroups::new(gpu);
96
97        Self {
98            render_pipeline,
99            _empty_group: empty_group,
100            locals_uniform,
101            locals_bind_groups,
102        }
103    }
104}
105impl PipelineRunner for OutlinePass {
106    type QueryItems<'a> = (
107        &'a VertsGPU,
108        &'a FacesGPU,
109        &'a NormalsGPU,
110        &'a VisMesh,
111        &'a Name,
112        &'a ModelMatrix,
113        &'a VisOutline,
114    );
115    type QueryState<'a> = gloss_hecs::QueryBorrow<'a, gloss_hecs::With<Self::QueryItems<'a>, &'a Renderable>>;
116
117    fn query_state(scene: &Scene) -> Self::QueryState<'_> {
118        scene.world.query::<Self::QueryItems<'_>>().with::<&Renderable>()
119    }
120
121    fn prepare<'a>(&mut self, gpu: &Gpu, _per_frame_uniforms: &PerFrameUniforms, scene: &'a Scene) -> Self::QueryState<'a> {
122        self.begin_pass();
123
124        self.update_locals(gpu, scene);
125
126        //update the bind group in case the input_texture or the shadowmaps changed
127        // self.update_input_bind_group(gpu, scene, per_frame_uniforms);
128
129        Self::query_state(scene)
130    }
131
132    /// # Panics
133    /// Will panic if the input bind groups are not created
134    #[allow(clippy::too_many_lines)]
135    fn run<'r>(
136        &'r mut self,
137        render_pass: &mut wgpu::RenderPass<'r>,
138        per_frame_uniforms: &'r PerFrameUniforms,
139        _render_params: &RenderConfig,
140        query_state: &'r mut Self::QueryState<'_>,
141        _scene: &Scene,
142    ) {
143        //completely skip this if there are no entities to draw
144        if query_state.iter().count() == 0 {
145            return;
146        }
147
148        // Draw outlines using stencil test
149        render_pass.set_pipeline(&self.render_pipeline);
150        render_pass.set_bind_group(0, &per_frame_uniforms.bind_group, &[]);
151        render_pass.set_stencil_reference(1); // Use 1 as the reference value
152
153        for (_id, (verts, faces, normals, vis_mesh, name, _model_matrix, vis_outline)) in query_state.iter() {
154            if !vis_mesh.show_mesh || !vis_outline.show_outline {
155                continue;
156            }
157
158            let (local_bg, offset) = &self.locals_bind_groups.mesh2local_bind[&name.0.clone()];
159
160            render_pass.set_bind_group(1, local_bg.bg(), &[*offset]);
161
162            render_pass.set_vertex_buffer(0, verts.buf.slice(..));
163            render_pass.set_vertex_buffer(1, normals.buf.slice(..));
164            render_pass.set_index_buffer(faces.buf.slice(..), wgpu::IndexFormat::Uint32);
165            render_pass.draw_indexed(0..faces.nr_triangles * 3, 0, 0..1);
166        }
167    }
168
169    fn begin_pass(&mut self) {}
170
171    /// update the local information that need to be sent to the gpu for each
172    /// mesh like te model matrix
173    fn update_locals(&mut self, gpu: &Gpu, scene: &Scene) {
174        Self::update_locals_inner::<Locals, _>(
175            gpu,
176            scene,
177            &mut self.locals_uniform,
178            &mut self.locals_bind_groups,
179            &mut Self::query_state(scene),
180        );
181    }
182}
183
184/// Keep in sync with shader `outline.wgsl`
185#[repr(C)]
186#[derive(Clone, Copy, encase::ShaderType)]
187struct Locals {
188    model_matrix: nalgebra::Matrix4<f32>,
189    outline_color: nalgebra::Vector4<f32>,
190    outline_width: f32,
191    is_floor: u32,
192    //wasm needs padding to 16 bytes https://github.com/gfx-rs/wgpu/issues/2932
193    // pad_b: f32,
194    pad_c: f32,
195    pad_d: f32,
196}
197impl LocalEntData for Locals {
198    fn new(entity: Entity, scene: &Scene) -> Self {
199        let model_matrix = scene.get_comp::<&ModelMatrix>(&entity).unwrap().0.to_homogeneous();
200
201        // Get outline properties from VisOutline component
202        let Ok(vis_outline) = scene.get_comp::<&VisOutline>(&entity) else {
203            // Default values if entity doesn't have VisOutline
204            let default_outline = VisOutline::default();
205            return Locals {
206                model_matrix,
207                outline_color: default_outline.outline_color,
208                outline_width: 0.0,
209                is_floor: 0,
210                pad_c: 0.0,
211                pad_d: 0.0,
212            };
213        };
214
215        let is_floor = if let Some(floor) = scene.get_floor() {
216            floor.entity == entity
217        } else {
218            false
219        };
220        let is_floor = u32::from(is_floor);
221
222        Locals {
223            model_matrix,
224            outline_color: vis_outline.outline_color,
225            outline_width: vis_outline.outline_width,
226            is_floor,
227            pad_c: 0.0,
228            pad_d: 0.0,
229        }
230    }
231}
232
233struct LocalsBindGroups {
234    layout: wgpu::BindGroupLayout,
235    pub mesh2local_bind: HashMap<String, (BindGroupWrapper, u32)>,
236}
237impl BindGroupCollection for LocalsBindGroups {
238    fn new(gpu: &Gpu) -> Self {
239        Self {
240            layout: Self::build_layout_desc().into_bind_group_layout(gpu.device()),
241            mesh2local_bind: HashMap::default(),
242        }
243    }
244
245    //keep as associated function so we can call it in the pipeline creation
246    // without and object
247    fn build_layout_desc() -> BindGroupLayoutDesc {
248        BindGroupLayoutBuilder::new()
249            .label("gbuffer_pass_locals_layout")
250            //locals
251            .add_entry_uniform(
252                wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
253                true,
254                wgpu::BufferSize::new(u64::from(align(u32::try_from(std::mem::size_of::<Locals>()).unwrap(), 256))),
255            )
256            .build()
257    }
258
259    fn update_bind_group(&mut self, _entity: Entity, gpu: &Gpu, mesh_name: &str, ubo: &Buffer, offset_in_ubo: u32, _scene: &Scene) {
260        let entries = BindGroupBuilder::new().add_entry_buf_chunk::<Locals>(&ubo.buffer).build_entries();
261
262        self.update_if_stale(mesh_name, entries, offset_in_ubo, gpu);
263    }
264
265    fn get_layout(&self) -> &wgpu::BindGroupLayout {
266        &self.layout
267    }
268    fn get_mut_entity2binds(&mut self) -> &mut HashMap<String, (BindGroupWrapper, u32)> {
269        &mut self.mesh2local_bind
270    }
271}