gloss_renderer/forward_renderer/render_passes/
entity_id_pass.rs

1use std::collections::HashMap;
2
3use crate::{
4    components::{FacesGPU, ModelMatrix, Name, Renderable, VertsGPU, VisMesh},
5    config::RenderConfig,
6    forward_renderer::{bind_group_collection::BindGroupCollection, locals::LocalEntData},
7    scene::Scene,
8    selector::Selector,
9};
10use easy_wgpu::{
11    bind_group::{BindGroupBuilder, BindGroupWrapper},
12    bind_group_layout::{BindGroupLayoutBuilder, BindGroupLayoutDesc},
13    buffer::Buffer,
14};
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/entity_id.wgsl")]
29mod shader_code {}
30
31/// Render all the meshes from the scene to the `GBuffer`
32pub struct EntityIdPass {
33    render_pipeline: wgpu::RenderPipeline,
34    _empty_group: wgpu::BindGroup,
35    locals_uniform: Buffer,
36    locals_bind_groups: LocalsBindGroups,
37}
38
39impl EntityIdPass {
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) -> Self {
44        //wasm likes everything to be 16 bytes aligned
45        const_assert!(std::mem::size_of::<Locals>() % 16 == 0);
46        /* ---------------------------------- NOTE ---------------------------------- */
47        // We explicitly set the format to `Rgba8Unorm` because we want to render the entity id to the screen for the selector
48        // Using depth format `Depth16Unorm` since its half the size of `Depth32Float`
49        // We cannot use the original depth buffer since that one is multi-sampled and we cant have one render target here
50        // be multi-sampled and the other not. We do not want to be multi-sampling the main target here since they are ID's
51        /* -------------------------------------------------------------------------- */
52
53        // Create the render pipeline for outlines
54        // The depth and Rgba8Unorm target are explicitly set since they are not prone to change
55        // We choose Rgba8Unorm as the target format due to WebGL restrictions on Firefox
56        // Firefox expects target to be Rgba8Unorm (RGBA/UNSIGNED_BYTE) or Rgba32Uint (RGBA_INTEGER/UNSIGNED_INT)
57        let render_pipeline = RenderPipelineDescBuilder::new()
58            .label("ent_id_pass")
59            .shader_code(shader_code::SOURCE)
60            .shader_label("ent_id_shader")
61            .add_bind_group_layout_desc(PerFrameUniforms::build_layout_desc())
62            .add_bind_group_layout_desc(LocalsBindGroups::build_layout_desc())
63            .add_vertex_buffer_layout(VertsGPU::vertex_buffer_layout::<0>())
64            .add_render_target(wgpu::ColorTargetState {
65                format: wgpu::TextureFormat::Rgba8Unorm,
66                blend: None,
67                write_mask: wgpu::ColorWrites::ALL,
68            })
69            .depth_state(Some(wgpu::DepthStencilState {
70                format: wgpu::TextureFormat::Depth16Unorm,
71                depth_write_enabled: true,
72                depth_compare: wgpu::CompareFunction::Greater,
73                stencil: wgpu::StencilState::default(),
74                bias: wgpu::DepthBiasState::default(),
75            }))
76            .build_pipeline(gpu.device());
77
78        let empty_group = create_empty_group(gpu.device());
79
80        let size_bytes = 0x10000;
81        let usage = wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM;
82        let locals_uniform = Buffer::new_empty(gpu.device(), usage, Some("local_buffer"), size_bytes);
83
84        let locals_bind_groups = LocalsBindGroups::new(gpu);
85
86        Self {
87            render_pipeline,
88            _empty_group: empty_group,
89            locals_uniform,
90            locals_bind_groups,
91        }
92    }
93}
94
95impl PipelineRunner for EntityIdPass {
96    type QueryItems<'a> = (&'a VertsGPU, &'a FacesGPU, &'a Name, &'a ModelMatrix, &'a VisMesh);
97    type QueryState<'a> = gloss_hecs::QueryBorrow<'a, gloss_hecs::With<Self::QueryItems<'a>, &'a Renderable>>;
98
99    fn query_state(scene: &Scene) -> Self::QueryState<'_> {
100        scene.world.query::<Self::QueryItems<'_>>().with::<&Renderable>()
101    }
102    fn prepare<'a>(&mut self, gpu: &Gpu, _per_frame_uniforms: &PerFrameUniforms, scene: &'a Scene) -> Self::QueryState<'a> {
103        self.begin_pass();
104        self.update_locals(gpu, scene);
105        Self::query_state(scene)
106    }
107
108    /// # Panics
109    /// Will panic if the input bind groups are not created
110    #[allow(clippy::too_many_lines)]
111    fn run<'r>(
112        &'r mut self,
113        render_pass: &mut wgpu::RenderPass<'r>,
114        per_frame_uniforms: &'r PerFrameUniforms,
115        _render_params: &RenderConfig,
116        query_state: &'r mut Self::QueryState<'_>,
117        scene: &Scene,
118    ) {
119        //completely skip this if there are no entities to draw or if the selector is not active
120        let selector_is_active = scene.has_resource::<Selector>();
121
122        if query_state.iter().count() == 0 || !selector_is_active {
123            return;
124        }
125
126        render_pass.set_pipeline(&self.render_pipeline);
127        render_pass.set_bind_group(0, &per_frame_uniforms.bind_group, &[]);
128
129        for (_id, (verts, faces, name, _model_matrix, _vis_mesh)) in query_state.iter() {
130            let (local_bg, offset) = &self.locals_bind_groups.mesh2local_bind[&name.0.clone()];
131
132            render_pass.set_bind_group(1, local_bg.bg(), &[*offset]);
133            render_pass.set_vertex_buffer(0, verts.buf.slice(..));
134            render_pass.set_index_buffer(faces.buf.slice(..), wgpu::IndexFormat::Uint32);
135            render_pass.draw_indexed(0..faces.nr_triangles * 3, 0, 0..1);
136        }
137    }
138
139    fn begin_pass(&mut self) {}
140
141    /// update the local information that need to be sent to the gpu for each
142    /// mesh like te model matrix
143    fn update_locals(&mut self, gpu: &Gpu, scene: &Scene) {
144        Self::update_locals_inner::<Locals, _>(
145            gpu,
146            scene,
147            &mut self.locals_uniform,
148            &mut self.locals_bind_groups,
149            &mut Self::query_state(scene),
150        );
151    }
152}
153
154/// Keep in sync with shader `ent_id.wgsl`
155#[repr(C)]
156#[derive(Clone, Copy, encase::ShaderType)]
157struct Locals {
158    model_matrix: nalgebra::Matrix4<f32>,
159    entity_id: u32,
160    pad_a: f32,
161    pad_b: f32,
162    pad_c: f32,
163}
164impl LocalEntData for Locals {
165    fn new(entity: Entity, scene: &Scene) -> Self {
166        let model_matrix = scene.get_comp::<&ModelMatrix>(&entity).unwrap().0.to_homogeneous();
167        assert!(entity.id() < 256, "A maximum of 256 entities are allowed!");
168        Locals {
169            model_matrix,
170            entity_id: entity.id(),
171            pad_a: 0.0,
172            pad_b: 0.0,
173            pad_c: 0.0,
174        }
175    }
176}
177
178struct LocalsBindGroups {
179    layout: wgpu::BindGroupLayout,
180    pub mesh2local_bind: HashMap<String, (BindGroupWrapper, u32)>,
181}
182impl BindGroupCollection for LocalsBindGroups {
183    fn new(gpu: &Gpu) -> Self {
184        Self {
185            layout: Self::build_layout_desc().into_bind_group_layout(gpu.device()),
186            mesh2local_bind: HashMap::default(),
187        }
188    }
189
190    //keep as associated function so we can call it in the pipeline creation
191    // without and object
192    fn build_layout_desc() -> BindGroupLayoutDesc {
193        BindGroupLayoutBuilder::new()
194            .label("ent_id_pass_locals_layout")
195            //locals
196            .add_entry_uniform(
197                wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
198                true,
199                wgpu::BufferSize::new(u64::from(align(u32::try_from(std::mem::size_of::<Locals>()).unwrap(), 256))),
200            )
201            .build()
202    }
203
204    fn update_bind_group(&mut self, _entity: Entity, gpu: &Gpu, mesh_name: &str, ubo: &Buffer, offset_in_ubo: u32, _scene: &Scene) {
205        let entries = BindGroupBuilder::new().add_entry_buf_chunk::<Locals>(&ubo.buffer).build_entries();
206
207        self.update_if_stale(mesh_name, entries, offset_in_ubo, gpu);
208    }
209
210    fn get_layout(&self) -> &wgpu::BindGroupLayout {
211        &self.layout
212    }
213    fn get_mut_entity2binds(&mut self) -> &mut HashMap<String, (BindGroupWrapper, u32)> {
214        &mut self.mesh2local_bind
215    }
216}