gloss_renderer/forward_renderer/render_passes/
upload_pass.rs

1#![allow(clippy::cast_precision_loss)]
2
3extern crate nalgebra as na;
4
5// use crate::backend_specific_action;
6
7use crate::{
8    camera::Camera,
9    components::{
10        Colors, ColorsGPU, DiffuseImg, DiffuseTex, Edges, EdgesV1, EdgesV1GPU, EdgesV2, EdgesV2GPU, EnvironmentMap, EnvironmentMapGpu, Faces,
11        FacesGPU, GpuAtrib, LightEmit, MeshColorType, Name, NormalImg, NormalTex, Normals, NormalsGPU, PosLookat, Projection, ProjectionWithFov,
12        Renderable, RoughnessImg, RoughnessTex, ShadowCaster, Tangents, TangentsGPU, UVs, UVsGPU, Verts, VertsGPU, VisMesh,
13    },
14    config::RenderConfig,
15    scene::Scene,
16};
17
18use easy_wgpu::{
19    bind_group::BindGroupBuilder,
20    bind_group_layout::{BindGroupLayoutBuilder, BindGroupLayoutDesc},
21    buffer::Buffer,
22    gpu::Gpu,
23    mipmap::RenderMipmapGenerator,
24    texture::Texture,
25};
26use gloss_utils::tensor::{DynamicMatrixOps, DynamicTensorFloat2D, DynamicTensorOps};
27
28use gloss_hecs::{Changed, CommandBuffer, Component, Entity};
29use gloss_utils::numerical::{align, align_usz};
30use log::{debug, info, warn};
31use std::collections::HashMap;
32use wgpu::util::DeviceExt;
33
34use encase;
35
36pub const MAX_NUM_LIGHTS: usize = 20; //lower than 20 causes wasm to throw error because the uniform is too small..
37pub const MAX_NUM_SHADOWS: usize = 3; //HAS to be lower than MAX_NUM_LIGHTS.
38
39pub fn index_vertices_from_edges(matrix: &na::DMatrix<f32>, v_indices: &na::DMatrix<u32>, col_id: usize) -> na::DMatrix<f32> {
40    let index_slice = v_indices.column(col_id).into_owned();
41    let indices: Vec<usize> = index_slice.iter().copied().map(|x| x as usize).collect();
42
43    // Select rows based on indices
44    let mut selected_rows = Vec::new();
45    for &index in &indices {
46        let row = matrix.row(index);
47        selected_rows.push(row);
48    }
49    na::DMatrix::from_rows(&selected_rows)
50}
51
52/// Upload pass which uploads to GPU any data that is necessary, like vertex
53/// buffers for meshes and camera parameters.
54pub struct UploadPass {
55    //all the buffers for per_frame stuff like light positions, cam parameters, etc. This are stuff that don't change from mesh to mesh
56    per_frame_uniforms: PerFrameUniforms,
57    mipmapper: Option<RenderMipmapGenerator>,
58    //the local stuff that changes from mesh to mesh is allocated by each pass, because each pass might need something different from the mesh
59    pub command_buffer: CommandBuffer, //defer insertions and deletion of scene entities for whenever we apply this command buffer
60    pub staging_buffer: Option<Buffer>,
61}
62
63impl UploadPass {
64    pub fn new(gpu: &Gpu, params: &RenderConfig) -> Self {
65        //wasm likes everything to be 16 bytes aligned
66        const_assert!(std::mem::size_of::<PerFrameSceneCPU>() % 16 == 0);
67        const_assert!(std::mem::size_of::<PerFrameCamCPU>() % 16 == 0);
68        const_assert!(std::mem::size_of::<PerFrameLightCPU>() % 16 == 0);
69        const_assert!(std::mem::size_of::<PerFrameParamsCPU>() % 16 == 0);
70
71        let per_frame_uniforms = PerFrameUniforms::new(gpu);
72
73        // cfg_if::cfg_if! {
74        //     if #[cfg(target_arch = "wasm32")] {
75        //         let mipmapper= None;
76        //     }else{
77        //         let mipmapper = Some(RenderMipmapGenerator::new_with_format_hints(
78        //             gpu.device(),
79        //             &[
80        //                 wgpu::TextureFormat::Rgba8Unorm, //for normal maps
81        //                 wgpu::TextureFormat::Rgba8UnormSrgb, //for diffuse maps
82        //                 wgpu::TextureFormat::R8Unorm, //for roughness maps
83        //             ],
84        //         ));
85        //     }
86        // }
87
88        let mipmapper = Some(RenderMipmapGenerator::new_with_format_hints(
89            gpu.device(),
90            &[
91                wgpu::TextureFormat::Rgba8Unorm,     //for normal maps
92                wgpu::TextureFormat::Rgba8UnormSrgb, //for diffuse maps
93                wgpu::TextureFormat::R8Unorm,        //for roughness maps
94            ],
95        ));
96
97        let command_buffer = CommandBuffer::new();
98
99        let staging_buffer = if params.preallocated_staging_buffer_bytes != 0 {
100            info!(
101                "Using preallocated staging buffer with {} MB",
102                params.preallocated_staging_buffer_bytes / (1024 * 1024)
103            );
104            Some(Buffer::new_empty(
105                gpu.device(),
106                wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::MAP_WRITE,
107                Some("gloss_staging_buffer"),
108                align_usz(params.preallocated_staging_buffer_bytes as usize, 256),
109            ))
110        } else {
111            None
112        };
113
114        Self {
115            per_frame_uniforms,
116            mipmapper,
117            command_buffer,
118            staging_buffer,
119        }
120    }
121
122    pub fn run(&mut self, gpu: &Gpu, camera: &Camera, scene: &mut Scene, render_params: &RenderConfig) -> &PerFrameUniforms {
123        //upload each component (all of these are needed for the mesh)
124        self.upload_v(gpu, scene);
125        self.upload_e(gpu, scene);
126        self.upload_f(gpu, scene);
127        self.upload_uv(gpu, scene);
128        self.upload_nv(gpu, scene);
129        self.upload_t(gpu, scene);
130        self.upload_c(gpu, scene);
131        self.upload_textures(gpu, scene);
132
133        self.upload_scene(gpu, scene);
134        self.upload_cam(gpu, camera, scene);
135        self.upload_lights(gpu, scene);
136        self.upload_params(gpu, scene, render_params);
137
138        &self.per_frame_uniforms
139    }
140
141    pub fn upload_textures(&mut self, gpu: &Gpu, scene: &mut Scene) {
142        self.upload_diffuse_tex(gpu, scene);
143        self.upload_normal_tex(gpu, scene);
144        self.upload_roughness_tex(gpu, scene);
145        self.upload_environment_map(gpu, scene);
146    }
147
148    #[allow(clippy::unnecessary_unwrap)] //I know it's unnecesary but it makes everything more compact the two cases
149                                         // more explicit
150    fn upload_dynamic_vertex_atrib<T, C: DynamicTensorOps<T> + Component, G: GpuAtrib + Component>(
151        &mut self,
152        entity: Entity,
153        atrib: &C,
154        atrib_gpu: Option<&mut G>,
155        gpu: &Gpu,
156        additional_usage: wgpu::BufferUsages, // scene: &mut Scene,
157        label: &str,
158    ) {
159        // TODO: If DynamicTensor of Wgpu backend, do a direct buffer to buffer copy
160        let verts_bytes = atrib.as_bytes();
161        let size_bytes = verts_bytes.len();
162        if atrib_gpu.is_none() || atrib_gpu.as_ref().unwrap().data_ref().size() != std::convert::TryInto::<u64>::try_into(size_bytes).unwrap() {
163            // Allocate new memory for the GPU buffer if it doesn't exist or size has
164            // changed
165            let desc = wgpu::util::BufferInitDescriptor {
166                label: Some(label),
167                contents: &verts_bytes, // Use the raw data directly
168                usage: additional_usage | wgpu::BufferUsages::COPY_DST,
169            };
170
171            let buf: wgpu::Buffer = gpu.device().create_buffer_init(&desc);
172
173            // Insert the new GPU buffer component into the entity
174            self.command_buffer
175                .insert_one(entity, G::new_from(buf, u32::try_from(atrib.nrows()).unwrap()));
176        } else {
177            gpu.queue().write_buffer(
178                atrib_gpu.unwrap().data_ref(),
179                0,
180                &verts_bytes, // Use the raw data directly
181            );
182        }
183    }
184
185    /// Functions for uploading each component of the mesh
186    fn upload_v(&mut self, gpu: &Gpu, scene: &mut Scene) {
187        let query = scene
188            .world
189            .query_mut::<(&Verts, Option<&mut VertsGPU>, Changed<Verts>)>()
190            .with::<&Renderable>();
191        let usage = wgpu::BufferUsages::VERTEX;
192
193        for (ent, (verts, mut verts_gpu, changed_verts)) in query {
194            if changed_verts {
195                self.upload_dynamic_vertex_atrib(ent, &verts.0, verts_gpu.as_deref_mut(), gpu, usage, "verts");
196            }
197        }
198        self.command_buffer.run_on(&mut scene.world);
199    }
200
201    fn upload_e(&mut self, gpu: &Gpu, scene: &mut Scene) {
202        let query = scene
203            .world
204            .query_mut::<(
205                &Verts,
206                &Edges,
207                Option<&mut EdgesV1GPU>,
208                Option<&mut EdgesV2GPU>,
209                Changed<Verts>,
210                Changed<Edges>,
211            )>()
212            .with::<&Renderable>();
213
214        let usage = wgpu::BufferUsages::VERTEX;
215        for (ent, (verts, edges, mut edges_v1_gpu, mut edges_v2_gpu, changed_verts, changed_edges)) in query {
216            if changed_verts || changed_edges {
217                let edges_v1_mat = index_vertices_from_edges(&verts.0.to_dmatrix(), &edges.0.to_dmatrix(), 0);
218                let edges_v2_mat = index_vertices_from_edges(&verts.0.to_dmatrix(), &edges.0.to_dmatrix(), 1);
219
220                let edges_v1_mat_tensor = DynamicTensorFloat2D::from_dmatrix(&edges_v1_mat);
221                let edges_v2_mat_tensor = DynamicTensorFloat2D::from_dmatrix(&edges_v2_mat);
222                let edges_v1 = EdgesV1(edges_v1_mat_tensor);
223                let edges_v2 = EdgesV2(edges_v2_mat_tensor);
224
225                self.upload_dynamic_vertex_atrib(ent, &edges_v1.0, edges_v1_gpu.as_deref_mut(), gpu, usage, "edges_v1");
226                self.upload_dynamic_vertex_atrib(ent, &edges_v2.0, edges_v2_gpu.as_deref_mut(), gpu, usage, "edges_v2");
227            }
228        }
229        self.command_buffer.run_on(&mut scene.world);
230    }
231
232    fn upload_f(&mut self, gpu: &Gpu, scene: &mut Scene) {
233        let query = scene
234            .world
235            .query_mut::<(&Faces, Option<&mut FacesGPU>, Changed<Faces>)>()
236            .with::<&Renderable>();
237        let usage = wgpu::BufferUsages::INDEX;
238        for (ent, (faces, mut faces_gpu, changed_faces)) in query {
239            if changed_faces {
240                self.upload_dynamic_vertex_atrib(ent, &faces.0, faces_gpu.as_deref_mut(), gpu, usage, "faces");
241            }
242        }
243        self.command_buffer.run_on(&mut scene.world);
244    }
245    fn upload_uv(&mut self, gpu: &Gpu, scene: &mut Scene) {
246        let query = scene.world.query_mut::<(&UVs, Option<&mut UVsGPU>, Changed<UVs>)>().with::<&Renderable>();
247        let usage = wgpu::BufferUsages::VERTEX;
248        for (ent, (uvs, mut uvs_gpu, changed_uvs)) in query {
249            if changed_uvs {
250                self.upload_dynamic_vertex_atrib(ent, &uvs.0, uvs_gpu.as_deref_mut(), gpu, usage, "uv");
251            }
252        }
253        self.command_buffer.run_on(&mut scene.world);
254    }
255
256    fn upload_nv(&mut self, gpu: &Gpu, scene: &mut Scene) {
257        let query = scene
258            .world
259            .query_mut::<(&Normals, Option<&mut NormalsGPU>, Changed<Normals>)>()
260            .with::<&Renderable>();
261        let usage = wgpu::BufferUsages::VERTEX;
262        for (ent, (normals, mut normals_gpu, changed_normals)) in query {
263            if changed_normals {
264                self.upload_dynamic_vertex_atrib(ent, &normals.0, normals_gpu.as_deref_mut(), gpu, usage, "normals");
265            }
266        }
267        self.command_buffer.run_on(&mut scene.world);
268    }
269
270    fn upload_t(&mut self, gpu: &Gpu, scene: &mut Scene) {
271        let query = scene
272            .world
273            .query_mut::<(&Tangents, Option<&mut TangentsGPU>, Changed<Tangents>)>()
274            .with::<&Renderable>();
275        let usage = wgpu::BufferUsages::VERTEX;
276        for (ent, (tangents, mut tangents_gpu, changed_tangents)) in query {
277            if changed_tangents {
278                self.upload_dynamic_vertex_atrib(ent, &tangents.0, tangents_gpu.as_deref_mut(), gpu, usage, "tangents");
279            }
280        }
281        self.command_buffer.run_on(&mut scene.world);
282    }
283
284    fn upload_c(&mut self, gpu: &Gpu, scene: &mut Scene) {
285        let query = scene
286            .world
287            .query_mut::<(&Colors, Option<&mut ColorsGPU>, Changed<Colors>)>()
288            .with::<&Renderable>();
289        let usage = wgpu::BufferUsages::VERTEX;
290        for (ent, (colors, mut colors_gpu, changed_colors)) in query {
291            if changed_colors {
292                self.upload_dynamic_vertex_atrib(ent, &colors.0, colors_gpu.as_deref_mut(), gpu, usage, "colors");
293            }
294        }
295        self.command_buffer.run_on(&mut scene.world);
296    }
297
298    fn upload_diffuse_tex(&mut self, gpu: &Gpu, scene: &mut Scene) {
299        let mut modified_entities = Vec::new();
300        {
301            let mut query = scene
302                .world
303                .query::<(&mut DiffuseImg, Option<&mut DiffuseTex>, Changed<DiffuseImg>)>()
304                .with::<&Renderable>();
305            for (entity, (mut img, tex_opt, changed_img)) in query.iter() {
306                if changed_img && img.generic_img.cpu_img.is_some() {
307                    debug!("DiffuseImg changed for entity {entity:?}");
308                    let nr_channels = img.generic_img.img_ref().color().channel_count();
309                    if nr_channels != 4 {
310                        warn!("unoptimal use of memory: diffuse does not have 4 channels, it has {nr_channels}");
311                    }
312                    modified_entities.push(entity);
313                    let is_srgb = true; //only true for diffuse since they are in srgb space in the png but we want to
314                                        // sample linear colors
315                                        // let tex = Texture::from_path(&img.0, gpu.device(), gpu.queue(), is_srgb);
316                    let keep_on_cpu = img.generic_img.config.keep_on_cpu;
317                    let staging_buffer = if img.generic_img.config.fast_upload {
318                        None
319                    } else {
320                        //using slow upload through a preallocated staging buffer
321                        if self.staging_buffer.is_none() {
322                            warn!("The diffuse image is set to slow upload which would require a preallocated staging buffer. However no bytes have been allocated for it. Check the config.toml for the preallocated_staging_buffer. Now we default to fast upload through wgpu staging buffer which might use more memory than necessary.");
323                        }
324                        self.staging_buffer.as_ref()
325                    };
326
327                    //either create a new tex or update the existing one
328                    let mut tex_uploaded = false;
329                    if let Some(mut existing_tex) = tex_opt {
330                        let new_tex_extent = Texture::extent_from_img(img.generic_img.img_ref());
331                        let new_tex_format = Texture::format_from_img(img.generic_img.img_ref(), is_srgb);
332                        let old_tex_extent = existing_tex.0.extent();
333                        let old_format = existing_tex.0.texture.format();
334                        if new_tex_format == old_format && new_tex_extent == old_tex_extent {
335                            debug!("reusing diffuse tex");
336                            existing_tex.0.update_from_img(
337                                img.generic_img.img_ref(),
338                                gpu.device(),
339                                gpu.queue(),
340                                is_srgb,
341                                img.generic_img.config.generate_mipmaps,
342                                img.generic_img.config.mipmap_generation_cpu,
343                                staging_buffer,
344                                self.mipmapper.as_ref(),
345                            );
346                            tex_uploaded = true;
347                        }
348                    }
349                    //we create a new one if we couldn't update an existing one
350                    if !tex_uploaded {
351                        let tex = Texture::from_img(
352                            img.generic_img.img_ref(),
353                            gpu.device(),
354                            gpu.queue(),
355                            is_srgb,
356                            img.generic_img.config.generate_mipmaps,
357                            img.generic_img.config.mipmap_generation_cpu,
358                            staging_buffer,
359                            self.mipmapper.as_ref(),
360                        );
361                        self.command_buffer.insert_one(entity, DiffuseTex(tex));
362                    }
363
364                    if !keep_on_cpu {
365                        // self.command_buffer.remove_one::<DiffuseImg>(entity);
366                        let _ = img.generic_img.cpu_img.take();
367                    }
368                }
369            }
370
371            //set those meshes to actually visualize the mesh
372            for entity in modified_entities {
373                // let mut vis_mesh = scene.get_comp::<&mut VisMesh>(&entity);
374                if let Ok(mut vis_mesh) = scene.get_comp::<&mut VisMesh>(&entity) {
375                    if vis_mesh.added_automatically {
376                        vis_mesh.color_type = MeshColorType::Texture;
377                    }
378                }
379            }
380        }
381        self.command_buffer.run_on(&mut scene.world);
382    }
383
384    fn upload_normal_tex(&mut self, gpu: &Gpu, scene: &mut Scene) {
385        let mut modified_entities = Vec::new();
386        {
387            let mut query = scene
388                .world
389                .query::<(&mut NormalImg, Option<&mut NormalTex>, Changed<NormalImg>)>()
390                .with::<&Renderable>();
391            for (entity, (mut img, tex_opt, changed_img)) in query.iter() {
392                if changed_img && img.generic_img.cpu_img.is_some() {
393                    debug!("NormalImg changed for entity {entity:?}");
394                    let nr_channels = img.generic_img.img_ref().color().channel_count();
395                    if nr_channels != 4 {
396                        warn!("unoptimal use of memory: normal does not have 4 channels, it has {nr_channels}");
397                    }
398                    modified_entities.push(entity);
399                    let is_srgb = false; //only true for diffuse since they are in srgb space in the png but we want to
400                                         // sample linear colors
401                    let keep_on_cpu = img.generic_img.config.keep_on_cpu;
402                    let staging_buffer = if img.generic_img.config.fast_upload {
403                        None
404                    } else {
405                        //using slow upload through a preallocated staging buffer
406                        if self.staging_buffer.is_none() {
407                            warn!("The normal image is set to slow upload which would require a preallocated staging buffer. However no bytes have been allocated for it. Check the config.toml for the preallocated_staging_buffer. Now we default to fast upload through wgpu staging buffer which might use more memory than necessary.");
408                        }
409                        self.staging_buffer.as_ref()
410                    };
411
412                    //either create a new tex or update the existing one
413                    let mut tex_uploaded = false;
414                    if let Some(mut existing_tex) = tex_opt {
415                        let new_tex_extent = Texture::extent_from_img(img.generic_img.img_ref());
416                        let new_tex_format = Texture::format_from_img(img.generic_img.img_ref(), is_srgb);
417                        let old_tex_extent = existing_tex.0.extent();
418                        let old_format = existing_tex.0.texture.format();
419                        if new_tex_format == old_format && new_tex_extent == old_tex_extent {
420                            debug!("reusing normal tex");
421                            existing_tex.0.update_from_img(
422                                img.generic_img.img_ref(),
423                                gpu.device(),
424                                gpu.queue(),
425                                is_srgb,
426                                img.generic_img.config.generate_mipmaps,
427                                img.generic_img.config.mipmap_generation_cpu,
428                                staging_buffer,
429                                self.mipmapper.as_ref(),
430                            );
431                            tex_uploaded = true;
432                        }
433                    }
434                    //we create a new one if we couldn't update an existing one
435                    if !tex_uploaded {
436                        let tex = Texture::from_img(
437                            img.generic_img.img_ref(),
438                            gpu.device(),
439                            gpu.queue(),
440                            is_srgb,
441                            img.generic_img.config.generate_mipmaps,
442                            img.generic_img.config.mipmap_generation_cpu,
443                            staging_buffer,
444                            self.mipmapper.as_ref(),
445                        );
446                        self.command_buffer.insert_one(entity, NormalTex(tex));
447                    }
448
449                    if !keep_on_cpu {
450                        // self.command_buffer.remove_one::<NormalImg>(entity);
451                        let _ = img.generic_img.cpu_img.take();
452                    }
453                }
454            }
455
456            //set those meshes to actually visualize the mesh
457            for entity in modified_entities {
458                if let Ok(mut vis_mesh) = scene.get_comp::<&mut VisMesh>(&entity) {
459                    if vis_mesh.added_automatically {
460                        vis_mesh.color_type = MeshColorType::Texture;
461                    }
462                }
463            }
464        }
465
466        self.command_buffer.run_on(&mut scene.world);
467    }
468
469    fn upload_roughness_tex(&mut self, gpu: &Gpu, scene: &mut Scene) {
470        let mut modified_entities = Vec::new();
471        {
472            let mut query = scene
473                .world
474                .query::<(&mut RoughnessImg, Option<&mut RoughnessTex>, Changed<RoughnessImg>)>()
475                .with::<&Renderable>();
476            for (entity, (mut img, tex_opt, changed_img)) in query.iter() {
477                if changed_img && img.generic_img.cpu_img.is_some() {
478                    debug!("RoughnessImg changed for entity {entity:?}");
479                    let nr_channels = img.generic_img.img_ref().color().channel_count();
480                    if nr_channels != 1 {
481                        warn!("unoptimal use of memory: roughness does not have 1 channels, it has {nr_channels}");
482                    }
483                    modified_entities.push(entity);
484                    let is_srgb = false; //only true for diffuse since they are in srgb space in the png but we want to
485                                         // sample linear colors
486                    let keep_on_cpu = img.generic_img.config.keep_on_cpu;
487                    let staging_buffer = if img.generic_img.config.fast_upload {
488                        None
489                    } else {
490                        //using slow upload through a preallocated staging buffer
491                        if self.staging_buffer.is_none() {
492                            warn!("The roughness image is set to slow upload which would require a preallocated staging buffer. However no bytes have been allocated for it. Check the config.toml for the preallocated_staging_buffer. Now we default to fast upload through wgpu staging buffer which might use more memory than necessary.");
493                        }
494                        self.staging_buffer.as_ref()
495                    };
496
497                    //either create a new tex or update the existing one
498                    let mut tex_uploaded = false;
499                    if let Some(mut existing_tex) = tex_opt {
500                        let new_tex_extent = Texture::extent_from_img(img.generic_img.img_ref());
501                        let new_tex_format = Texture::format_from_img(img.generic_img.img_ref(), is_srgb);
502                        let old_tex_extent = existing_tex.0.extent();
503                        let old_format = existing_tex.0.texture.format();
504                        if new_tex_format == old_format && new_tex_extent == old_tex_extent {
505                            debug!("reusing roughness tex");
506                            existing_tex.0.update_from_img(
507                                img.generic_img.img_ref(),
508                                gpu.device(),
509                                gpu.queue(),
510                                is_srgb,
511                                img.generic_img.config.generate_mipmaps,
512                                img.generic_img.config.mipmap_generation_cpu,
513                                staging_buffer,
514                                self.mipmapper.as_ref(),
515                            );
516                            tex_uploaded = true;
517                        }
518                    }
519                    //we create a new one if we couldn't update an existing one
520                    if !tex_uploaded {
521                        let tex = Texture::from_img(
522                            img.generic_img.img_ref(),
523                            gpu.device(),
524                            gpu.queue(),
525                            is_srgb,
526                            img.generic_img.config.generate_mipmaps,
527                            img.generic_img.config.mipmap_generation_cpu,
528                            staging_buffer,
529                            self.mipmapper.as_ref(),
530                        );
531                        self.command_buffer.insert_one(entity, RoughnessTex(tex));
532                    }
533
534                    if !keep_on_cpu {
535                        // self.command_buffer.remove_one::<RoughnessImg>(entity);
536                        let _ = img.generic_img.cpu_img.take();
537                    }
538                }
539            }
540
541            //set those meshes to actually visualize the mesh
542            for entity in modified_entities {
543                if let Ok(mut vis_mesh) = scene.get_comp::<&mut VisMesh>(&entity) {
544                    if vis_mesh.added_automatically {
545                        vis_mesh.color_type = MeshColorType::Texture;
546                    }
547                }
548            }
549        }
550
551        self.command_buffer.run_on(&mut scene.world);
552    }
553
554    fn upload_environment_map(&mut self, gpu: &Gpu, scene: &mut Scene) {
555        // if scene.has_resource::<EnvironmentMap>() {
556        let query = scene.world.query_mut::<(&EnvironmentMap, Changed<EnvironmentMap>)>();
557        for (entity, (env_map, changed_env)) in query {
558            if changed_env {
559                let diffue_raw_data = std::fs::read(env_map.diffuse_path.clone()).unwrap();
560                let diffuse_reader = ktx2::Reader::new(diffue_raw_data.as_slice()).expect("Can't create diffuse_reader");
561                let specular_raw_data = std::fs::read(env_map.specular_path.clone()).unwrap();
562                let specular_reader = ktx2::Reader::new(specular_raw_data.as_slice()).expect("Can't create specular_reader");
563
564                let diffuse_tex = EnvironmentMapGpu::reader2texture(&diffuse_reader, gpu.device(), gpu.queue());
565                let specular_tex = EnvironmentMapGpu::reader2texture(&specular_reader, gpu.device(), gpu.queue());
566
567                let env_map_gpu = EnvironmentMapGpu { diffuse_tex, specular_tex };
568
569                // scene.add_resource(env_map);
570                self.command_buffer.insert_one(entity, env_map_gpu);
571            }
572        }
573
574        self.command_buffer.run_on(&mut scene.world);
575    }
576
577    fn upload_scene(&mut self, gpu: &Gpu, scene: &mut Scene) {
578        let entities_lights = scene.get_lights(false);
579        let env_map = scene.get_resource::<&EnvironmentMapGpu>().unwrap();
580        let environment_map_smallest_specular_mip_level = env_map.specular_tex.texture.mip_level_count() - 1;
581
582        let per_frame_scene_data = PerFrameSceneCPU {
583            nr_lights: u32::try_from(entities_lights.len()).unwrap(),
584            environment_map_smallest_specular_mip_level,
585            pad_1: 0,
586            pad_2: 0,
587        };
588
589        self.per_frame_uniforms.scene_buf.push_cpu_chunk_packed(&per_frame_scene_data);
590        self.per_frame_uniforms.scene_buf.upload_from_cpu_chunks(gpu.queue());
591        self.per_frame_uniforms.scene_buf.reset_chunks_offset();
592    }
593
594    fn upload_cam(&mut self, gpu: &Gpu, camera: &Camera, scene: &mut Scene) {
595        let pos_lookat = if let Ok(pos_lookat) = scene.world.get::<&mut PosLookat>(camera.entity) {
596            pos_lookat.clone()
597        } else {
598            PosLookat::default()
599        };
600
601        let view_matrix = pos_lookat.view_matrix();
602        let view_inv_matrix = pos_lookat.view_matrix_isometry().inverse().to_matrix();
603
604        //get projection info but also take into account that if there are no entities
605        // yet in the scene, there is also no projection matrix so we just set some
606        // reasonable defaults
607        let proj_matrix;
608        let near;
609        let far;
610        if scene.world.has::<Projection>(camera.entity).unwrap() {
611            proj_matrix = camera.proj_matrix_reverse_z(scene);
612            (near, far) = camera.near_far(scene);
613        } else {
614            let proj = ProjectionWithFov::default();
615            proj_matrix = proj.proj_matrix_reverse_z();
616            (near, far) = (proj.near, proj.far);
617        }
618        let (width, height) = camera.get_target_res(scene);
619        let aspect_ratio = width as f32 / height as f32;
620        let proj_inv_matrix = proj_matrix.try_inverse().unwrap();
621
622        let vp_matrix = proj_matrix * view_matrix;
623        let pos_world = pos_lookat.position.coords;
624
625        #[allow(clippy::cast_precision_loss)]
626        let per_frame_cam_data = PerFrameCamCPU {
627            view_matrix,
628            view_inv_matrix,
629            proj_matrix,
630            proj_inv_matrix,
631            vp_matrix,
632            pos_world,
633            near,
634            far,
635            aspect_ratio,
636            width: width as f32,
637            height: height as f32,
638        };
639
640        self.per_frame_uniforms.cam_buf.push_cpu_chunk_packed(&per_frame_cam_data);
641        self.per_frame_uniforms.cam_buf.upload_from_cpu_chunks(gpu.queue());
642        self.per_frame_uniforms.cam_buf.reset_chunks_offset();
643    }
644
645    fn upload_lights(&mut self, gpu: &Gpu, scene: &mut Scene) {
646        self.per_frame_uniforms.idx_ubo2light.clear();
647
648        let query = scene
649            .world
650            .query_mut::<(&Name, &PosLookat, &Projection, &LightEmit, Option<&ShadowCaster>)>();
651        for (idx_light, (entity, (name, pos_lookat, proj, light_emit, shadow_caster))) in query.into_iter().enumerate() {
652            let view_matrix = pos_lookat.view_matrix();
653            let proj_matrix = match *proj {
654                Projection::WithFov(ref proj) => proj.proj_matrix_reverse_z(),
655                Projection::WithIntrinsics(_) => {
656                    panic!("We don't deal with light that have projection as intrinsics")
657                }
658            };
659            let (near, far) = proj.near_far();
660            let vp_matrix = proj_matrix * view_matrix;
661            let pos_world = pos_lookat.position.coords;
662            let lookat_dir_world = pos_lookat.direction();
663
664            let color = light_emit.color;
665            let intensity = light_emit.intensity;
666            let range = light_emit.range;
667            let inverse_square_range = 1.0 / (range * range);
668            let radius = light_emit.radius;
669            let is_shadow_casting_bool = shadow_caster.is_some();
670            let is_shadow_casting: u32 = u32::from(is_shadow_casting_bool);
671
672            let shadow_bias_fixed = if let Some(shadow_caster) = shadow_caster {
673                shadow_caster.shadow_bias_fixed
674            } else {
675                0.0
676            };
677            let shadow_bias = if let Some(shadow_caster) = shadow_caster {
678                shadow_caster.shadow_bias
679            } else {
680                0.0
681            };
682            let shadow_bias_normal = if let Some(shadow_caster) = shadow_caster {
683                shadow_caster.shadow_bias_normal
684            } else {
685                0.0
686            };
687
688            // let outer_angle = proj.fovy / 2.0; //we can use the fov as the angle because
689            // we know the fov_y is the same as fov_x because the shadowmaps are  always
690            // square let outer_angle = 1.57; //we can use the fov as the angle
691            // because we know the fov_y is the same as fov_x because the shadowmaps are
692            // always square
693            let outer_angle = light_emit.outer_angle;
694            let inner_angle = light_emit.inner_angle;
695
696            //encase
697            let per_frame_light_data = PerFrameLightCPU {
698                view_matrix,
699                proj_matrix,
700                vp_matrix,
701                pos_world,
702                lookat_dir_world,
703                color,
704                intensity,
705                range,
706                inverse_square_range,
707                radius,
708                // spot_scale: 1.0,
709                outer_angle,
710                inner_angle,
711                near,
712                far,
713                is_shadow_casting,
714                shadow_bias_fixed,
715                shadow_bias,
716                shadow_bias_normal,
717                pad_b: 1.0,
718                pad_c: 1.0,
719                pad_d: 1.0,
720            };
721
722            //push packed because we will expose it as an array inside the shader
723            self.per_frame_uniforms.lights_buf.push_cpu_chunk_packed(&per_frame_light_data);
724
725            //save also a mapping between light name and the idx in the whole light buffer
726            self.per_frame_uniforms
727                .light2idx_ubo
728                .insert(name.0.clone(), u32::try_from(idx_light).unwrap());
729            self.per_frame_uniforms.idx_ubo2light.push(entity);
730        }
731
732        self.per_frame_uniforms.lights_buf.upload_from_cpu_chunks(gpu.queue());
733        self.per_frame_uniforms.lights_buf.reset_chunks_offset();
734    }
735
736    fn upload_params(&mut self, gpu: &Gpu, _scene: &mut Scene, render_params: &RenderConfig) {
737        let per_frame_params_data = PerFrameParamsCPU {
738            ambient_factor: render_params.ambient_factor,
739            environment_factor: render_params.environment_factor,
740            bg_color: render_params.bg_color,
741            enable_distance_fade: u32::from(render_params.enable_distance_fade.unwrap_or(false)),
742            distance_fade_center: render_params.distance_fade_center.unwrap_or_default().coords,
743            distance_fade_start: render_params.distance_fade_start.unwrap_or(0.0),
744            distance_fade_end: render_params.distance_fade_end.unwrap_or(0.0),
745            apply_lighting: u32::from(render_params.apply_lighting),
746            saturation: render_params.saturation,
747            gamma: render_params.gamma,
748            exposure: render_params.exposure,
749            shadow_filter_method: render_params.shadow_filter_method as i32, // post_saturation: render_params.post_saturation,
750            pad_b: 0.0,
751            pad_c: 0.0,
752            pad_d: 0.0,
753        };
754
755        self.per_frame_uniforms.params_buf.push_cpu_chunk_packed(&per_frame_params_data);
756        self.per_frame_uniforms.params_buf.upload_from_cpu_chunks(gpu.queue());
757        self.per_frame_uniforms.params_buf.reset_chunks_offset();
758    }
759}
760
761#[repr(C)]
762#[derive(Clone, Copy, encase::ShaderType)]
763struct PerFrameSceneCPU {
764    nr_lights: u32,
765    environment_map_smallest_specular_mip_level: u32,
766    //wasm needs padding to 16 bytes https://github.com/gfx-rs/wgpu/issues/2932
767    pad_1: u32,
768    pad_2: u32,
769}
770/// Contains camera data that will be sent to the GPU once a frame.
771#[repr(C)]
772#[derive(Clone, Copy, encase::ShaderType)]
773struct PerFrameCamCPU {
774    view_matrix: na::Matrix4<f32>,
775    view_inv_matrix: na::Matrix4<f32>,
776    proj_matrix: na::Matrix4<f32>,
777    proj_inv_matrix: na::Matrix4<f32>,
778    vp_matrix: na::Matrix4<f32>, /* proj*view //order matter because we multiply from the left this matrix so we first do the view_matrix and then
779                                  * proj */
780    pos_world: na::Vector3<f32>,
781    near: f32,
782    far: f32,
783    aspect_ratio: f32,
784    width: f32,
785    height: f32,
786}
787/// Contains light data that will be sent to the GPU once a frame.
788#[repr(C)]
789#[derive(Clone, Copy, encase::ShaderType)]
790// #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
791struct PerFrameLightCPU {
792    view_matrix: na::Matrix4<f32>,
793    proj_matrix: na::Matrix4<f32>,
794    vp_matrix: na::Matrix4<f32>, /* proj*view //order matter because we multiply from the left this matrix so we first do the view_matrix and then
795                                  * proj */
796    pos_world: na::Vector3<f32>,
797    lookat_dir_world: na::Vector3<f32>,
798    color: na::Vector3<f32>,
799    intensity: f32,
800    range: f32,
801    inverse_square_range: f32, //just 1/(range*range) because we don't want to compute this on gpu
802    radius: f32,
803    outer_angle: f32,
804    inner_angle: f32,
805    near: f32,
806    far: f32,
807    is_shadow_casting: u32, //should be bool but that is not host-sharable: https://www.w3.org/TR/WGSL/#host-shareable-types
808    shadow_bias_fixed: f32,
809    shadow_bias: f32,
810    shadow_bias_normal: f32,
811    //wasm needs padding to 16 bytes https://github.com/gfx-rs/wgpu/issues/2932
812    pad_b: f32,
813    pad_c: f32,
814    pad_d: f32,
815}
816#[repr(C)]
817#[derive(Clone, Copy, encase::ShaderType)]
818struct PerFrameParamsCPU {
819    ambient_factor: f32,
820    environment_factor: f32,
821    bg_color: na::Vector4<f32>,
822    enable_distance_fade: u32,
823    distance_fade_center: na::Vector3<f32>,
824    distance_fade_start: f32,
825    distance_fade_end: f32,
826    //color grading, applied before tonemapping
827    apply_lighting: u32,
828    saturation: f32,
829    gamma: f32,
830    exposure: f32,
831    shadow_filter_method: i32,
832    // post_saturation: f32, //applied after tonemapping
833    //wasm needs padding to 16 bytes https://github.com/gfx-rs/wgpu/issues/2932
834    pad_b: f32,
835    pad_c: f32,
836    pad_d: f32,
837}
838
839/// All the buffers that are the same for all meshes. Contains things like
840/// camera parameters, lights, and global setting.
841#[non_exhaustive]
842pub struct PerFrameUniforms {
843    scene_buf: Buffer,  //group 0, binding 0
844    cam_buf: Buffer,    //group 0, binding 1
845    lights_buf: Buffer, //group 0, binding 2
846    params_buf: Buffer, //group 0, binding 3
847
848    #[allow(dead_code)]
849    //storing the samplers is not needed since the bind group consumes them but it makes things more explicit
850    sampler_nearest: wgpu::Sampler, //group 0, binding 4
851    #[allow(dead_code)]
852    sampler_linear: wgpu::Sampler, //group 0, binding 5
853    #[allow(dead_code)]
854    sampler_comparison: wgpu::Sampler, //group 0, binding 6
855    //we save also the bind_group since we will not need to recreate it (the buffer will not be reallocated)
856    //the layout we keep as a associated function because we want to call it without the object.
857    pub bind_group: wgpu::BindGroup,
858    //misc
859    pub light2idx_ubo: HashMap<String, u32>,
860    pub idx_ubo2light: Vec<Entity>,
861}
862impl PerFrameUniforms {
863    pub fn new(gpu: &Gpu) -> Self {
864        let scene_buf = Buffer::new_empty(
865            gpu.device(),
866            wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
867            Some("global_scene_uniform"),
868            align_usz(std::mem::size_of::<PerFrameSceneCPU>(), 256),
869        );
870        //allocate buffers on gpu to hold the corresponding cpu data
871        let cam_buf = Buffer::new_empty(
872            gpu.device(),
873            wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
874            Some("global_cam_uniform"),
875            align_usz(std::mem::size_of::<PerFrameCamCPU>(), 256),
876        );
877        //allocate space fo MAX_NUM_LIGHTS lights
878        let lights_buf = Buffer::new_empty(
879            gpu.device(),
880            wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
881            // wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
882            Some("global_lights_uniform"),
883            MAX_NUM_LIGHTS * align_usz(std::mem::size_of::<PerFrameLightCPU>(), 256),
884        );
885        let params_buf = Buffer::new_empty(
886            gpu.device(),
887            wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
888            Some("global_params_uniform"),
889            align_usz(std::mem::size_of::<PerFrameParamsCPU>(), 256),
890        );
891
892        //samplers for nearest and linear
893        let sampler_nearest = gpu.device().create_sampler(&wgpu::SamplerDescriptor {
894            label: Some("sampler_nearest"),
895            address_mode_u: wgpu::AddressMode::Repeat,
896            address_mode_v: wgpu::AddressMode::Repeat,
897            address_mode_w: wgpu::AddressMode::Repeat,
898            min_filter: wgpu::FilterMode::Nearest,
899            mag_filter: wgpu::FilterMode::Nearest,
900            mipmap_filter: wgpu::FilterMode::Nearest,
901            ..Default::default()
902        });
903        let sampler_linear = gpu.device().create_sampler(&wgpu::SamplerDescriptor {
904            label: Some("sampler_linear"),
905            address_mode_u: wgpu::AddressMode::Repeat,
906            address_mode_v: wgpu::AddressMode::Repeat,
907            address_mode_w: wgpu::AddressMode::Repeat,
908            min_filter: wgpu::FilterMode::Linear,
909            mag_filter: wgpu::FilterMode::Linear,
910            mipmap_filter: wgpu::FilterMode::Linear,
911            ..Default::default()
912        });
913        let sampler_comparison = gpu.device().create_sampler(&wgpu::SamplerDescriptor {
914            label: Some("sampler_shadow_map"),
915            min_filter: wgpu::FilterMode::Linear,
916            mag_filter: wgpu::FilterMode::Linear,
917            compare: Some(wgpu::CompareFunction::Greater),
918            ..Default::default()
919        });
920
921        let layout = Self::create_layout(gpu);
922        let bind_group = BindGroupBuilder::new()
923            .label("per_frame_bind_group")
924            .add_entry_buf(&scene_buf.buffer)
925            .add_entry_buf(&cam_buf.buffer)
926            .add_entry_buf(&lights_buf.buffer)
927            .add_entry_buf(&params_buf.buffer)
928            .add_entry_sampler(&sampler_nearest)
929            .add_entry_sampler(&sampler_linear)
930            .add_entry_sampler(&sampler_comparison)
931            .build_bind_group(gpu.device(), &layout);
932
933        Self {
934            scene_buf,
935            cam_buf,
936            lights_buf,
937            params_buf,
938            sampler_nearest,
939            sampler_linear,
940            sampler_comparison,
941            bind_group,
942            light2idx_ubo: HashMap::new(),
943            idx_ubo2light: Vec::new(),
944        }
945    }
946
947    //keep as associated function so we can call it in the pipeline creation
948    // without and object
949    pub fn create_layout(gpu: &Gpu) -> wgpu::BindGroupLayout {
950        let global_bind_group_layout = Self::build_layout_desc().into_bind_group_layout(gpu.device());
951        global_bind_group_layout
952    }
953
954    /// # Panics
955    /// Will panic if the texture is deleted while it's being copied
956    pub fn build_layout_desc() -> BindGroupLayoutDesc {
957        BindGroupLayoutBuilder::new()
958            .label("locals_layout")
959            //scene
960            .add_entry_uniform(
961                wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
962                false,
963                wgpu::BufferSize::new(u64::from(align(u32::try_from(std::mem::size_of::<PerFrameSceneCPU>()).unwrap(), 256))),
964            )
965            //cam
966            .add_entry_uniform(
967                wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
968                false,
969                wgpu::BufferSize::new(u64::from(align(u32::try_from(std::mem::size_of::<PerFrameCamCPU>()).unwrap(), 256))),
970            )
971            //light
972            .add_entry_uniform(
973                wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
974                false,
975                wgpu::BufferSize::new(u64::from(align(u32::try_from(std::mem::size_of::<PerFrameLightCPU>()).unwrap(), 256))),
976            )
977            //params
978            .add_entry_uniform(
979                wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
980                false,
981                wgpu::BufferSize::new(u64::from(align(u32::try_from(std::mem::size_of::<PerFrameParamsCPU>()).unwrap(), 256))),
982            )
983            //samplers
984            .add_entry_sampler(wgpu::ShaderStages::FRAGMENT, wgpu::SamplerBindingType::NonFiltering)
985            .add_entry_sampler(wgpu::ShaderStages::FRAGMENT, wgpu::SamplerBindingType::Filtering)
986            .add_entry_sampler(wgpu::ShaderStages::FRAGMENT, wgpu::SamplerBindingType::Comparison)
987            .build()
988    }
989}