From e2c6b557bb578fe9bc6963b009b027b9e94e4be5 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Mon, 22 Apr 2024 01:07:35 -0400 Subject: [PATCH] render: improve performance of transform interpolation by using ecs components --- Cargo.lock | 1 + lyra-ecs/src/query/mod.rs | 4 + lyra-ecs/src/query/optional.rs | 76 +++++++++++++++++ lyra-game/Cargo.toml | 1 + lyra-game/src/render/renderer.rs | 142 +++++++++++++++---------------- 5 files changed, 153 insertions(+), 71 deletions(-) create mode 100644 lyra-ecs/src/query/optional.rs diff --git a/Cargo.lock b/Cargo.lock index 6df55fd..3691e9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1842,6 +1842,7 @@ dependencies = [ "lyra-resource", "lyra-scene", "quote", + "rustc-hash", "syn 2.0.51", "thiserror", "tracing", diff --git a/lyra-ecs/src/query/mod.rs b/lyra-ecs/src/query/mod.rs index 8738608..fa39907 100644 --- a/lyra-ecs/src/query/mod.rs +++ b/lyra-ecs/src/query/mod.rs @@ -27,6 +27,10 @@ mod world; #[allow(unused_imports)] pub use world::*; +mod optional; +#[allow(unused_imports)] +pub use optional::*; + pub mod dynamic; pub mod filter; diff --git a/lyra-ecs/src/query/optional.rs b/lyra-ecs/src/query/optional.rs new file mode 100644 index 0000000..e6c59ca --- /dev/null +++ b/lyra-ecs/src/query/optional.rs @@ -0,0 +1,76 @@ +use crate::{Archetype, World}; + +use super::{AsQuery, Fetch, Query}; + +#[derive(Default)] +pub struct OptionalFetcher<'a, Q: AsQuery> { + fetcher: Option<::Fetch<'a>>, +} + +impl<'a, Q: AsQuery> Fetch<'a> for OptionalFetcher<'a, Q> { + type Item = Option<::Item<'a>>; + + fn dangling() -> Self { + unreachable!() + } + + unsafe fn get_item(&mut self, entity: crate::ArchetypeEntityId) -> Self::Item { + self.fetcher.as_mut() + .map(|f| f.get_item(entity)) + } + + fn can_visit_item(&mut self, entity: crate::ArchetypeEntityId) -> bool { + self.fetcher.as_mut() + .map(|f| f.can_visit_item(entity)) + .unwrap_or(true) + } +} + +#[derive(Default)] +pub struct Optional { + query: Q::Query, +} + +impl Copy for Optional { } + +impl Clone for Optional { + fn clone(&self) -> Self { + Self { query: self.query.clone() } + } +} + +impl Query for Optional { + type Item<'a> = Option<::Item<'a>>; + + type Fetch<'a> = OptionalFetcher<'a, Q>; + + fn new() -> Self { + Optional { + query: Q::Query::new(), + } + } + + fn can_visit_archetype(&self, _: &Archetype) -> bool { + true + } + + unsafe fn fetch<'a>(&self, world: &'a World, arch: &'a Archetype, tick: crate::Tick) -> Self::Fetch<'a> { + let fetcher = if self.query.can_visit_archetype(arch) { + Some(self.query.fetch(world, arch, tick)) + } else { + None + }; + + OptionalFetcher { + fetcher, + } + } +} + +impl AsQuery for Optional { + type Query = Self; +} + +impl AsQuery for Option { + type Query = Optional; +} \ No newline at end of file diff --git a/lyra-game/Cargo.toml b/lyra-game/Cargo.toml index d4e6009..0ea0b05 100644 --- a/lyra-game/Cargo.toml +++ b/lyra-game/Cargo.toml @@ -34,6 +34,7 @@ uuid = { version = "1.5.0", features = ["v4", "fast-rng"] } itertools = "0.11.0" thiserror = "1.0.56" unique = "0.9.1" +rustc-hash = "1.1.0" [features] tracy = ["dep:tracing-tracy"] diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index d4f95d8..cd7f127 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -1,15 +1,14 @@ -use std::collections::{HashMap, VecDeque, HashSet}; +use std::collections::{VecDeque, HashSet}; use std::rc::Rc; use std::sync::Arc; use std::borrow::Cow; use glam::Vec3; -use instant::Instant; use itertools::izip; use lyra_ecs::query::filter::{Has, Not, Or}; use lyra_ecs::relation::{ChildOf, RelationOriginComponent}; -use lyra_ecs::{Entity, Tick}; -use lyra_ecs::query::{Entities, TickOf}; +use lyra_ecs::{Component, Entity}; +use lyra_ecs::query::{Entities, Res, TickOf}; use lyra_ecs::World; use lyra_scene::{SceneGraph, WorldTransform}; use tracing::{debug, instrument, warn}; @@ -22,6 +21,7 @@ use crate::math::Transform; use crate::render::material::MaterialUniform; use crate::render::render_buffer::BufferWrapperBuilder; use crate::scene::CameraComponent; +use crate::DeltaTime; use super::camera::{RenderCamera, CameraUniform}; use super::desc_buf_lay::DescVertexBufferLayout; @@ -67,12 +67,10 @@ struct MeshBufferStorage { //transform_index: TransformBufferIndices, } -#[derive(Clone, Debug)] -pub struct CachedTransform { - last_updated_at: Option, - cached_at: Instant, - to_transform: Transform, - from_transform: Transform, +#[derive(Clone, Debug, Component)] +pub struct InterpTransform { + last_transform: Transform, + alpha: f32, } pub struct BasicRenderer { @@ -85,13 +83,12 @@ pub struct BasicRenderer { pub clear_color: wgpu::Color, - pub render_pipelines: HashMap>, + pub render_pipelines: rustc_hash::FxHashMap>, pub render_jobs: VecDeque, - mesh_buffers: HashMap, // TODO: clean up left over buffers from deleted entities/components - material_buffers: HashMap>, - entity_meshes: HashMap, - entity_last_transforms: HashMap, + mesh_buffers: rustc_hash::FxHashMap, // TODO: clean up left over buffers from deleted entities/components + material_buffers: rustc_hash::FxHashMap>, + entity_meshes: rustc_hash::FxHashMap, transform_buffers: TransformBuffers, @@ -223,11 +220,11 @@ impl BasicRenderer { b: 0.3, a: 1.0, }, - render_pipelines: HashMap::new(), - render_jobs: VecDeque::new(), - mesh_buffers: HashMap::new(), - material_buffers: HashMap::new(), - entity_meshes: HashMap::new(), + render_pipelines: Default::default(), + render_jobs: Default::default(), + mesh_buffers: Default::default(), + material_buffers: Default::default(), + entity_meshes: Default::default(), render_limits, transform_buffers, @@ -238,7 +235,6 @@ impl BasicRenderer { bgl_texture, default_texture, depth_buffer_texture: depth_texture, - entity_last_transforms: HashMap::new(), light_buffers: light_uniform_buffers, material_buffer: mat_buffer, @@ -246,7 +242,7 @@ impl BasicRenderer { }; // create the default pipelines - let mut pipelines = HashMap::new(); + let mut pipelines = rustc_hash::FxHashMap::default(); pipelines.insert(0, Arc::new(FullRenderPipeline::new(&s.device, &s.config, &shader, vec![super::vertex::Vertex::desc(),], vec![&s.bgl_texture, &s.transform_buffers.bindgroup_layout, @@ -398,64 +394,68 @@ impl BasicRenderer { true } else { false } } - - #[instrument(skip(self, now, transform, entity))] - fn interpolate_transforms(&mut self, now: Instant, last_epoch: Tick, entity: Entity, transform: &Transform, transform_epoch: Tick) -> Transform { - let cached = match self.entity_last_transforms.get_mut(&entity) { - Some(last) if transform_epoch == last_epoch => { - last.from_transform = last.to_transform; - last.to_transform = *transform; - last.last_updated_at = Some(last.cached_at); - last.cached_at = now; - - last.clone() - }, - Some(last) => last.clone(), - None => { - let cached = CachedTransform { - last_updated_at: None, - cached_at: now, - from_transform: *transform, - to_transform: *transform, - }; - self.entity_last_transforms.insert(entity, cached.clone()); - cached - } - }; - - let fixed_time = match cached.last_updated_at { - Some(last_updated_at) => cached.cached_at - last_updated_at, - None => now - cached.cached_at - }.as_secs_f32(); - let accumulator = (now - cached.cached_at).as_secs_f32(); - let alpha = accumulator / fixed_time; - - cached.from_transform.lerp(cached.to_transform, alpha) - } } impl Renderer for BasicRenderer { #[instrument(skip(self, main_world))] fn prepare(&mut self, main_world: &mut World) { let last_epoch = main_world.current_tick(); - let now_inst = Instant::now(); let mut alive_entities = HashSet::new(); - let view = main_world.view_iter::<(Entities, &Transform, TickOf, - Or<(&MeshHandle, TickOf), (&SceneHandle, TickOf)>)>(); + let view = main_world.view_iter::<( + Entities, + &Transform, + TickOf, + Or< + (&MeshHandle, TickOf), + (&SceneHandle, TickOf) + >, + Option<&mut InterpTransform>, + Res, + )>(); - for (entity, transform, transform_epoch, (mesh_pair, scene_pair)) in view { + // used to store InterpTransform components to add to entities later + let mut component_queue: Vec<(Entity, InterpTransform)> = vec![]; + + for ( + entity, + transform, + _transform_epoch, + ( + mesh_pair, + scene_pair + ), + interp_tran, + delta_time, + ) in view + { alive_entities.insert(entity); - if let Some((mesh_han, mesh_epoch)) = mesh_pair { - // TODO: speed up interpolating transforms - let interop_pos = *transform; //self.interpolate_transforms(now_inst, last_epoch, entity, &transform, transform_epoch); + let interp_transform = match interp_tran { + Some(mut interp_transform) => { + // found in https://youtu.be/YJB1QnEmlTs?t=472 + interp_transform.alpha = 1.0 - interp_transform.alpha.powf(**delta_time); + + interp_transform.last_transform = interp_transform.last_transform.lerp(*transform, interp_transform.alpha); + interp_transform.last_transform + }, + None => { + let interp = InterpTransform { + last_transform: *transform, + alpha: 0.5, + }; + component_queue.push((entity, interp)); + *transform + } + }; + + if let Some((mesh_han, mesh_epoch)) = mesh_pair { if let Some(mesh) = mesh_han.data_ref() { // if process mesh did not just create a new mesh, and the epoch // shows that the scene has changed, verify that the mesh buffers // dont need to be resent to the gpu. - if !self.process_mesh(entity, interop_pos, &*mesh, mesh_han.uuid()) + if !self.process_mesh(entity, interp_transform, &*mesh, mesh_han.uuid()) && mesh_epoch == last_epoch { self.check_mesh_buffers(entity, &mesh_han); } @@ -466,7 +466,7 @@ impl Renderer for BasicRenderer { let group = TransformGroup::EntityRes(entity, mesh_han.uuid()); let transform_id = self.transform_buffers.update_or_push(&self.device, &self.queue, &self.render_limits, - group, interop_pos.calculate_mat4(), glam::Mat3::from_quat(interop_pos.rotation)); + group, interp_transform.calculate_mat4(), glam::Mat3::from_quat(interp_transform.rotation)); let material = mesh.material.as_ref().unwrap() .data_ref().unwrap(); @@ -483,12 +483,9 @@ impl Renderer for BasicRenderer { lyra_scene::system_update_world_transforms(scene.world(), view).unwrap(); } - // TODO: speed up interpolating transforms - let interpo_pos = *transform; //self.interpolate_transforms(now_inst, last_epoch, entity, &transform, transform_epoch); - for (mesh_han, pos) in scene.world().view_iter::<(&MeshHandle, &WorldTransform)>() { if let Some(mesh) = mesh_han.data_ref() { - let mesh_interpo = interpo_pos + **pos; + let mesh_interpo = interp_transform + **pos; // if process mesh did not just create a new mesh, and the epoch // shows that the scene has changed, verify that the mesh buffers @@ -518,6 +515,10 @@ impl Renderer for BasicRenderer { } } + for (en, interp) in component_queue { + main_world.insert(en, interp); + } + // collect dead entities self.transform_buffers.send_to_gpu(&self.queue); @@ -531,10 +532,9 @@ impl Renderer for BasicRenderer { self.mesh_buffers.retain(|u, _| !removed_entities.contains(u)); } + // update camera uniform if let Some(camera) = main_world.view_iter::<&mut CameraComponent>().next() { let uniform = self.inuse_camera.calc_view_projection(&camera); - //let pos = camera.transform.translation; - //let uniform = CameraUniform::new(view_mat, *view_proj, pos); self.camera_buffer.write_buffer(&self.queue, 0, &[uniform]); } else { warn!("Missing camera!");