render: improve performance of transform interpolation by using ecs components

This commit is contained in:
SeanOMik 2024-04-22 01:07:35 -04:00
parent 337ce18e8c
commit e2c6b557bb
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
5 changed files with 153 additions and 71 deletions

1
Cargo.lock generated
View File

@ -1842,6 +1842,7 @@ dependencies = [
"lyra-resource",
"lyra-scene",
"quote",
"rustc-hash",
"syn 2.0.51",
"thiserror",
"tracing",

View File

@ -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;

View File

@ -0,0 +1,76 @@
use crate::{Archetype, World};
use super::{AsQuery, Fetch, Query};
#[derive(Default)]
pub struct OptionalFetcher<'a, Q: AsQuery> {
fetcher: Option<<Q::Query as Query>::Fetch<'a>>,
}
impl<'a, Q: AsQuery> Fetch<'a> for OptionalFetcher<'a, Q> {
type Item = Option<<Q::Query as Query>::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<Q: AsQuery> {
query: Q::Query,
}
impl<Q: AsQuery> Copy for Optional<Q> { }
impl<Q: AsQuery> Clone for Optional<Q> {
fn clone(&self) -> Self {
Self { query: self.query.clone() }
}
}
impl<Q: AsQuery> Query for Optional<Q> {
type Item<'a> = Option<<Q::Query as Query>::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<Q: AsQuery> AsQuery for Optional<Q> {
type Query = Self;
}
impl<Q: AsQuery> AsQuery for Option<Q> {
type Query = Optional<Q>;
}

View File

@ -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"]

View File

@ -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<Instant>,
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<u64, Arc<FullRenderPipeline>>,
pub render_pipelines: rustc_hash::FxHashMap<u64, Arc<FullRenderPipeline>>,
pub render_jobs: VecDeque<RenderJob>,
mesh_buffers: HashMap<uuid::Uuid, MeshBufferStorage>, // TODO: clean up left over buffers from deleted entities/components
material_buffers: HashMap<uuid::Uuid, Rc<Material>>,
entity_meshes: HashMap<Entity, uuid::Uuid>,
entity_last_transforms: HashMap<Entity, CachedTransform>,
mesh_buffers: rustc_hash::FxHashMap<uuid::Uuid, MeshBufferStorage>, // TODO: clean up left over buffers from deleted entities/components
material_buffers: rustc_hash::FxHashMap<uuid::Uuid, Rc<Material>>,
entity_meshes: rustc_hash::FxHashMap<Entity, uuid::Uuid>,
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<Transform>,
Or<(&MeshHandle, TickOf<MeshHandle>), (&SceneHandle, TickOf<SceneHandle>)>)>();
let view = main_world.view_iter::<(
Entities,
&Transform,
TickOf<Transform>,
Or<
(&MeshHandle, TickOf<MeshHandle>),
(&SceneHandle, TickOf<SceneHandle>)
>,
Option<&mut InterpTransform>,
Res<DeltaTime>,
)>();
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!");