diff --git a/Cargo.lock b/Cargo.lock index 6cc0899..e5d9d61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -996,6 +996,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fps_counter" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff23a4d90ba4b859f370ee3c12ca3b1ca80d8ee144b279e135f6852cdadd6dd6" +dependencies = [ + "instant", +] + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -1950,6 +1959,18 @@ dependencies = [ "libc", ] +[[package]] +name = "many-lights" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-std", + "fps_counter", + "lyra-engine", + "rand 0.8.5", + "tracing", +] + [[package]] name = "matchers" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 4091b3d..f285537 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,11 +10,12 @@ members = [ "lyra-ecs", "lyra-reflect", "lyra-scripting", - "lyra-game", "lyra-math", "lyra-scene"] + "lyra-game", "lyra-math", "lyra-scene", "examples/many-lights"] [features] scripting = ["dep:lyra-scripting"] lua_scripting = ["scripting", "lyra-scripting/lua"] +tracy = ["lyra-game/tracy"] [dependencies] lyra-game = { path = "lyra-game" } diff --git a/examples/many-lights/Cargo.toml b/examples/many-lights/Cargo.toml new file mode 100644 index 0000000..e8e5536 --- /dev/null +++ b/examples/many-lights/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "many-lights" +version = "0.1.0" +edition = "2021" + +[dependencies] +lyra-engine = { path = "../../", features = ["tracy"] } +anyhow = "1.0.75" +async-std = "1.12.0" +tracing = "0.1.37" +rand = "0.8.5" +fps_counter = "3.0.0" + +[target.x86_64-unknown-linux-gnu] +linker = "/usr/bin/clang" +rustflags = ["-Clink-arg=-fuse-ld=lld", "-Clink-arg=-Wl,--no-rosegment"] + +[profile.release] +debug = true \ No newline at end of file diff --git a/examples/many-lights/src/main.rs b/examples/many-lights/src/main.rs new file mode 100644 index 0000000..a3b17a2 --- /dev/null +++ b/examples/many-lights/src/main.rs @@ -0,0 +1,189 @@ +use lyra_engine::{assets::{gltf::Gltf, ResourceManager}, ecs::query::{Res, ResMut, View}, game::Game, input::{Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput}, math::{self, Quat, Transform, Vec3}, render::light::{directional::DirectionalLight, PointLight}, scene::{CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN}, DeltaTime}; +use rand::Rng; +use tracing::info; + +const MAX_POINT_LIGHT_RANGE: f32 = 1.0; +const MIN_POINT_LIGHT_RANGE: f32 = 0.5; +const POINT_LIGHT_MAX_INTENSITY: f32 = 1.0; +const POINT_LIGHT_MIN_INTENSITY: f32 = 0.3; +const POINT_LIGHT_CUBE_SCALE: f32 = 0.2; +const POINT_LIGHT_NUM: u32 = 500; + +const POINT_LIGHT_MAX_X: f32 = 9.0; +const POINT_LIGHT_MIN_X: f32 = -9.0; + +const POINT_LIGHT_MAX_Y: f32 = 3.0; +const POINT_LIGHT_MIN_Y: f32 = 0.5; + +const POINT_LIGHT_MAX_Z: f32 = 4.0; +const POINT_LIGHT_MIN_Z: f32 = -5.0; + +#[async_std::main] +async fn main() { + + let action_handler_plugin = |game: &mut Game| { + let action_handler = ActionHandler::builder() + .add_layout(LayoutId::from(0)) + + .add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_MOVE_LEFT_RIGHT, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_MOVE_UP_DOWN, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_LOOK_LEFT_RIGHT, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_LOOK_UP_DOWN, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_LOOK_ROLL, Action::new(ActionKind::Axis)) + .add_action("Debug", Action::new(ActionKind::Button)) + + .add_mapping(ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0)) + .bind(ACTLBL_MOVE_FORWARD_BACKWARD, &[ + ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0), + ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0) + ]) + .bind(ACTLBL_MOVE_LEFT_RIGHT, &[ + ActionSource::Keyboard(KeyCode::A).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::D).into_binding_modifier(1.0) + ]) + .bind(ACTLBL_MOVE_UP_DOWN, &[ + ActionSource::Keyboard(KeyCode::C).into_binding_modifier(1.0), + ActionSource::Keyboard(KeyCode::Z).into_binding_modifier(-1.0) + ]) + .bind(ACTLBL_LOOK_LEFT_RIGHT, &[ + ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(), + ActionSource::Keyboard(KeyCode::Left).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::Right).into_binding_modifier(1.0), + ]) + .bind(ACTLBL_LOOK_UP_DOWN, &[ + ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(), + ActionSource::Keyboard(KeyCode::Up).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::Down).into_binding_modifier(1.0), + ]) + .bind(ACTLBL_LOOK_ROLL, &[ + ActionSource::Keyboard(KeyCode::E).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::Q).into_binding_modifier(1.0), + ]) + .bind("Debug", &[ + ActionSource::Keyboard(KeyCode::B).into_binding(), + ]) + .finish() + ).finish(); + + let world = game.world_mut(); + world.add_resource(action_handler); + game.with_plugin(InputActionPlugin); + }; + + Game::initialize().await + .with_plugin(lyra_engine::DefaultPlugins) + .with_plugin(setup_scene_plugin) + .with_plugin(action_handler_plugin) + .with_plugin(camera_debug_plugin) + .with_plugin(FreeFlyCameraPlugin) + .run().await; +} + +fn setup_scene_plugin(game: &mut Game) { + let fps_counter = |mut counter: ResMut, delta: Res| -> anyhow::Result<()> { + let tick = counter.tick(); + + info!("FPS: {}, frame time: {}", tick, **delta); + + Ok(()) + }; + game.with_system("fps_counter", fps_counter, &[]); + + let world = game.world_mut(); + world.add_resource(fps_counter::FPSCounter::new()); + + let resman = world.get_resource_mut::(); + let cube_gltf = resman.request::("../assets/texture-sep/texture-sep.gltf").unwrap(); + + cube_gltf.wait_recurse_dependencies_load(); + let cube_mesh = &cube_gltf.data_ref() + .unwrap().meshes[0]; + + let sponza_model = resman.request::("../assets/sponza/Sponza.gltf").unwrap(); + drop(resman); + + sponza_model.wait_recurse_dependencies_load(); + let sponza_scene = &sponza_model.data_ref() + .unwrap().scenes[0]; + + world.spawn(( + sponza_scene.clone(), + Transform::from_xyz(0.0, 0.0, 0.0), + )); + + { + let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0); + light_tran.scale = Vec3::new(0.5, 0.5, 0.5); + light_tran.rotate_x(math::Angle::Degrees(-45.0)); + light_tran.rotate_y(math::Angle::Degrees(25.0)); + world.spawn(( + DirectionalLight { + enabled: true, + color: Vec3::ONE, + intensity: 0.15 + //..Default::default() + }, + light_tran, + )); + } + + let x_range = POINT_LIGHT_MIN_X..POINT_LIGHT_MAX_X; + let y_range = POINT_LIGHT_MIN_Y..POINT_LIGHT_MAX_Y; + let z_range = POINT_LIGHT_MIN_Z..POINT_LIGHT_MAX_Z; + + let mut rand = rand::thread_rng(); + let mut rand_vec3 = || -> Vec3 { + let x = rand.gen_range(x_range.clone()); + let y = rand.gen_range(y_range.clone()); + let z = rand.gen_range(z_range.clone()); + + Vec3::new(x, y, z) + }; + + let mut rand = rand::thread_rng(); + for _ in 0..POINT_LIGHT_NUM { + let range = rand.gen_range(MIN_POINT_LIGHT_RANGE..MAX_POINT_LIGHT_RANGE); + let intensity = rand.gen_range(POINT_LIGHT_MIN_INTENSITY..POINT_LIGHT_MAX_INTENSITY); + let color = rand_vec3().normalize(); + + world.spawn(( + PointLight { + enabled: true, + color, + intensity, + range, + ..Default::default() + }, + Transform::new( + rand_vec3(), + Quat::IDENTITY, + Vec3::new(POINT_LIGHT_CUBE_SCALE, POINT_LIGHT_CUBE_SCALE, POINT_LIGHT_CUBE_SCALE), + ), + cube_mesh.clone(), + )); + } + + let mut camera = CameraComponent::new_3d(); + // these values were taken by manually positioning the camera in the scene. + camera.transform = Transform::new( + Vec3::new(-10.0, 0.94, -0.28), + Quat::from_xyzw(0.03375484, -0.7116095, 0.0342693, 0.70092666), + Vec3::ONE + ); + world.spawn(( camera, FreeFlyCamera::default() )); +} + +fn camera_debug_plugin(game: &mut Game) { + let sys = |handler: Res, view: View<&mut CameraComponent>| -> anyhow::Result<()> { + if handler.was_action_just_pressed("Debug") { + for mut cam in view.into_iter() { + cam.debug = !cam.debug; + } + } + + Ok(()) + }; + + game.with_system("camera_debug_trigger", sys, &[]); +} \ No newline at end of file diff --git a/lyra-game/src/game.rs b/lyra-game/src/game.rs index d9e036e..5e624d7 100755 --- a/lyra-game/src/game.rs +++ b/lyra-game/src/game.rs @@ -349,7 +349,7 @@ impl Game { .with(fmt::layer().with_writer(stdout_layer)); #[cfg(feature = "tracy")] - t.with(tracing_tracy::TracyLayer::default()); + let t = t.with(tracing_tracy::TracyLayer::default()); t.with(filter::Targets::new() // done by prefix, so it includes all lyra subpackages diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index ff79b1a..cca268b 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -458,6 +458,10 @@ impl Renderer for BasicRenderer { && mesh_epoch == last_epoch { self.check_mesh_buffers(entity, &mesh_han); } + + if self.transform_buffers.needs_expand() { + self.transform_buffers.expand_buffers(&self.device); + } let group = TransformGroup::EntityRes(entity, mesh_han.uuid()); let transform_id = self.transform_buffers.update_or_push(&self.queue, &self.render_limits, @@ -492,6 +496,10 @@ impl Renderer for BasicRenderer { self.check_mesh_buffers(entity, &mesh_han); } + if self.transform_buffers.needs_expand() { + self.transform_buffers.expand_buffers(&self.device); + } + let scene_mesh_group = TransformGroup::Res(scene_han.uuid(), mesh_han.uuid()); let group = TransformGroup::OwnedGroup(entity, scene_mesh_group.into()); let transform_id = self.transform_buffers.update_or_push(&self.queue, &self.render_limits, diff --git a/lyra-game/src/render/transform_buffer_storage.rs b/lyra-game/src/render/transform_buffer_storage.rs index bd9e85d..b62d3fd 100644 --- a/lyra-game/src/render/transform_buffer_storage.rs +++ b/lyra-game/src/render/transform_buffer_storage.rs @@ -370,6 +370,13 @@ impl TransformBuffers { pub fn buffer_offset(&self, transform_index: TransformIndex) -> u32 { Self::get_buffer_offset(&self.limits, transform_index) } + + /// Returns a boolean indicating if the buffers need to be expanded + pub fn needs_expand(&self) -> bool { + self.entries.last() + .map(|entry| entry.len >= self.max_transform_count) + .unwrap_or(false) + } } #[repr(C)] diff --git a/lyra-game/src/stage.rs b/lyra-game/src/stage.rs index 3c5ceb8..2f82381 100644 --- a/lyra-game/src/stage.rs +++ b/lyra-game/src/stage.rs @@ -1,7 +1,7 @@ use std::{hash::{Hash, DefaultHasher, Hasher}, collections::{HashMap, HashSet, VecDeque}, ptr::NonNull, fmt::Debug}; use lyra_ecs::{system::{GraphExecutor, GraphExecutorError, System}, World}; -use tracing::info_span; +use tracing::{info_span, instrument}; #[derive(thiserror::Error, Debug)] pub enum StagedExecutorError { @@ -112,6 +112,7 @@ impl StagedExecutor { /// Execute the staged systems in order. /// /// If `stop_on_error` is false but errors are encountered, those errors will be returned in a Vec. + #[instrument(skip(self, world, stop_on_error))] pub fn execute(&mut self, world: NonNull, stop_on_error: bool) -> Result, StagedExecutorError> { let mut stack = VecDeque::new(); let mut visited = HashSet::new(); @@ -120,13 +121,14 @@ impl StagedExecutor { self.topological_sort(&mut stack, &mut visited, node)?; } - let stage_span = info_span!("stage_exec", stage=tracing::field::Empty); + //let stage_span = info_span!("stage_exec", stage=tracing::field::Empty); let mut errors = vec![]; while let Some(node) = stack.pop_front() { let stage = self.stages.get_mut(&node).unwrap(); - stage_span.record("stage", stage.name.clone()); + let stage_span = info_span!("stage_exec", stage=stage.name.clone()); + //stage_span.record("stage", stage.name.clone()); let _e = stage_span.enter(); if let Err(e) = stage.exec.execute(world, stop_on_error) {