diff --git a/src/main.rs b/src/main.rs index 3fa1b2b..b65a93c 100755 --- a/src/main.rs +++ b/src/main.rs @@ -9,37 +9,24 @@ mod physics; mod picking; mod postprocess; mod render; -mod snow; -mod snow_light; mod state; mod systems; mod texture; mod utility; mod world; -use crate::debug::{collider_debug, DebugMode}; -use crate::editor::{editor_loop, EditorState, FrameStats}; - -use std::time::{Duration, Instant}; - -use glam::Vec3; -use render::Renderer; -use sdl3::event::Event; -use sdl3::keyboard::Keycode; -use sdl3::mouse::MouseButton; -use utility::input::InputState; -use world::World; - use crate::bundles::camera::CameraBundle; use crate::bundles::player::PlayerBundle; use crate::bundles::spotlight::spawn_spotlights; use crate::bundles::terrain::{TerrainBundle, TerrainConfig}; use crate::bundles::test_char::TestCharBundle; use crate::bundles::Bundle; +use crate::debug::{collider_debug, DebugMode}; +use crate::editor::{editor_loop, EditorState, FrameStats}; +use crate::entity::EntityHandle; use crate::loaders::scene::Space; use crate::physics::PhysicsManager; -use crate::snow::{SnowConfig, SnowLayer}; - +use crate::render::snow::{SnowConfig, SnowLayer}; use crate::systems::camera::stop_camera_following; use crate::systems::{ camera_follow_system, camera_input_system, camera_view_matrix, dialog_bubble_render_system, @@ -49,9 +36,34 @@ use crate::systems::{ tree_dissolve_update_system, tree_instance_buffer_update_system, tree_occlusion_system, trigger_system, }; +use crate::utility::input::InputState; use crate::utility::time::Time; -fn main() -> Result<(), Box> +use std::time::{Duration, Instant}; + +use glam::Vec3; +use render::Renderer; +use sdl3::event::Event; +use sdl3::keyboard::Keycode; +use sdl3::mouse::MouseButton; +use world::World; + +struct Game +{ + sdl_context: sdl3::Sdl, + window: sdl3::video::Window, + _event_pump: sdl3::EventPump, + world: World, + editor: EditorState, + input_state: InputState, + camera_entity: EntityHandle, + last_frame: Instant, + frame_duration: Duration, + physics_accumulator: f32, + stats: FrameStats, +} + +fn init() -> Result> { let sdl_context = sdl3::init()?; let video_subsystem = sdl_context.video()?; @@ -72,34 +84,69 @@ fn main() -> Result<(), Box> }); editor.init_platform(&window); + let (mut world, camera_entity) = init_world()?; + start_camera_following(&mut world, camera_entity); + + let _event_pump = sdl_context.event_pump()?; + let input_state = InputState::new(); + + sdl_context.mouse().set_relative_mouse_mode(&window, true); + + Time::init(); + + Ok(Game { + sdl_context, + window, + _event_pump, + world, + editor, + input_state, + camera_entity, + last_frame: Instant::now(), + frame_duration: Duration::from_millis(1000 / 60), + physics_accumulator: 0.0, + stats: FrameStats { + fps: 0.0, + frame_ms: 0.0, + physics_budget_ms: 0.0, + draw_call_count: 0, + }, + }) +} + +fn init_world() -> Result<(World, EntityHandle), Box> +{ let space = Space::load_space(&crate::paths::meshes::terrain())?; let terrain_config = TerrainConfig::default(); - let player_spawn = space.player_spawn; - let camera_spawn = space.camera_spawn_position(); - let tree_positions: Vec = space .mesh_data .iter() .flat_map(|(_, instances)| instances.iter().map(|inst| inst.position)) .collect(); + let player_spawn = space.player_spawn; + let test_char_spawn = space.test_char_spawn; + let camera_spawn = space.camera_spawn_position(); + let spotlights = space.spotlights; + let mesh_data = space.mesh_data; + let mut world = World::new(); - let _player_entity = PlayerBundle { + PlayerBundle { position: player_spawn, } .spawn(&mut world) .unwrap(); - let _test_char_entity = TestCharBundle { - position: space.test_char_spawn, + TestCharBundle { + position: test_char_spawn, } .spawn(&mut world) .unwrap(); - let _terrain_entity = TerrainBundle::spawn(&mut world, space.mesh_data, &terrain_config)?; - spawn_spotlights(&mut world, space.spotlights); + TerrainBundle::spawn(&mut world, mesh_data, &terrain_config)?; + spawn_spotlights(&mut world, spotlights); render::set_terrain_data(); @@ -110,294 +157,320 @@ fn main() -> Result<(), Box> ); let snow_config = SnowConfig::default(); - let mut snow_layer = SnowLayer::load(&snow_config)?; + let snow_layer = SnowLayer::load(&snow_config)?; for pos in &tree_positions { snow_layer.deform_at_position(*pos, 5.0, 50.0); } println!("Snow layer loaded successfully"); - render::set_snow_depth(&snow_layer.depth_texture_view); - - let mut debug_mode = DebugMode::default(); + world.snow_layer = Some(snow_layer); let camera_entity = CameraBundle { position: camera_spawn, } .spawn(&mut world) .unwrap(); - start_camera_following(&mut world, camera_entity); - let _event_pump = sdl_context.event_pump()?; - let mut input_state = InputState::new(); + Ok((world, camera_entity)) +} - sdl_context.mouse().set_relative_mouse_mode(&window, true); +fn process_events(game: &mut Game) -> bool +{ + game.editor.begin_frame(); - Time::init(); - let mut last_frame = Instant::now(); - let target_fps = 60; - let frame_duration = Duration::from_millis(1000 / target_fps); - - const FIXED_TIMESTEP: f32 = 1.0 / 60.0; - let mut physics_accumulator = 0.0; - - let mut stats = FrameStats { - fps: 0.0, - frame_ms: 0.0, - physics_budget_ms: 0.0, - draw_call_count: 0, - }; - - 'running: loop + while let Some(raw_event) = dear_imgui_sdl3::sdl3_poll_event_ll() { - let frame_start = Instant::now(); - let time = Time::get_time_elapsed(); - let delta = (frame_start - last_frame).as_secs_f32(); - last_frame = frame_start; + game.editor.process_event(&raw_event); + let event = Event::from_ll(raw_event); - editor.begin_frame(); - - while let Some(raw_event) = dear_imgui_sdl3::sdl3_poll_event_ll() + match &event { - editor.process_event(&raw_event); - let event = Event::from_ll(raw_event); - - match &event + Event::Quit { .. } => { - Event::Quit { .. } => - { - input_state.quit_requested = true; - continue; - } - - Event::KeyDown { - keycode: Some(Keycode::Tab), - repeat: false, - .. - } => - { - editor.active = !editor.active; - if editor.active - { - stop_camera_following(&mut world, camera_entity); - sdl_context.mouse().set_relative_mouse_mode(&window, false); - editor.right_mouse_held = false; - input_state.mouse_captured = false; - } - else - { - start_camera_following(&mut world, camera_entity); - input_state.mouse_captured = true; - sdl_context.mouse().set_relative_mouse_mode(&window, true); - } - continue; - } - - Event::MouseButtonDown { - mouse_btn: MouseButton::Right, - .. - } if editor.active => - { - editor.right_mouse_held = true; - input_state.mouse_captured = true; - stop_camera_following(&mut world, camera_entity); - sdl_context.mouse().set_relative_mouse_mode(&window, true); - continue; - } - - Event::MouseButtonUp { - mouse_btn: MouseButton::Right, - .. - } if editor.active => - { - editor.right_mouse_held = false; - input_state.mouse_captured = false; - sdl_context.mouse().set_relative_mouse_mode(&window, false); - continue; - } - - Event::MouseButtonDown { - mouse_btn: MouseButton::Left, - x, - y, - .. - } if editor.active && !editor.wants_mouse() => - { - if let Some(view) = crate::systems::camera_view_matrix(&world) - { - if let Some((_, cam)) = world.active_camera() - { - let projection = cam.projection_matrix(); - let (win_w, win_h) = window.size(); - let ray = crate::picking::Ray::from_screen_position( - *x, - *y, - win_w, - win_h, - &view, - &projection, - ); - editor.selected_entity = crate::picking::pick_entity(&ray, &world); - render::set_selected_entity(editor.selected_entity); - } - } - continue; - } - - _ => - {} + return true; } - if editor.active && (editor.wants_keyboard() || editor.wants_mouse()) + Event::KeyDown { + keycode: Some(Keycode::Tab), + repeat: false, + .. + } => { + toggle_editor(game); continue; } - let capture_changed = input_state.handle_event(&event); - if capture_changed && !editor.active + Event::MouseButtonDown { + mouse_btn: MouseButton::Right, + .. + } if game.editor.active => { - sdl_context + game.editor.right_mouse_held = true; + game.input_state.mouse_captured = true; + stop_camera_following(&mut game.world, game.camera_entity); + game.sdl_context .mouse() - .set_relative_mouse_mode(&window, input_state.mouse_captured); + .set_relative_mouse_mode(&game.window, true); + continue; } + + Event::MouseButtonUp { + mouse_btn: MouseButton::Right, + .. + } if game.editor.active => + { + game.editor.right_mouse_held = false; + game.input_state.mouse_captured = false; + game.sdl_context + .mouse() + .set_relative_mouse_mode(&game.window, false); + continue; + } + + Event::MouseButtonDown { + mouse_btn: MouseButton::Left, + x, + y, + .. + } if game.editor.active && !game.editor.wants_mouse() => + { + handle_editor_pick(game, *x, *y); + continue; + } + + _ => + {} } - if input_state.quit_requested + if game.editor.active && (game.editor.wants_keyboard() || game.editor.wants_mouse()) { - break 'running; + continue; } - if input_state.debug_cycle_just_pressed + let capture_changed = game.input_state.handle_event(&event); + if capture_changed && !game.editor.active { - debug_mode = debug_mode.cycle(); - println!("Debug mode: {:?}", debug_mode); + game.sdl_context + .mouse() + .set_relative_mouse_mode(&game.window, game.input_state.mouse_captured); + } + } + + false +} + +fn toggle_editor(game: &mut Game) +{ + game.editor.active = !game.editor.active; + if game.editor.active + { + stop_camera_following(&mut game.world, game.camera_entity); + game.sdl_context + .mouse() + .set_relative_mouse_mode(&game.window, false); + game.editor.right_mouse_held = false; + game.input_state.mouse_captured = false; + } + else + { + start_camera_following(&mut game.world, game.camera_entity); + game.input_state.mouse_captured = true; + game.sdl_context + .mouse() + .set_relative_mouse_mode(&game.window, true); + } +} + +fn handle_editor_pick(game: &mut Game, x: f32, y: f32) +{ + let view = match camera_view_matrix(&game.world) + { + Some(v) => v, + None => return, + }; + let (_, cam) = match game.world.active_camera() + { + Some(c) => c, + None => return, + }; + let projection = cam.projection_matrix(); + let (win_w, win_h) = game.window.size(); + let ray = picking::Ray::from_screen_position(x, y, win_w, win_h, &view, &projection); + game.editor.selected_entity = picking::pick_entity(&ray, &game.world); + render::set_selected_entity(game.editor.selected_entity); +} + +fn submit_frame(game: &mut Game, draw_calls: &[render::DrawCall], time: f32, delta: f32) +{ + let (camera_entity, camera_component) = match game.world.active_camera() + { + Some(c) => c, + None => return, + }; + let camera_transform = match game.world.transforms.get(camera_entity) + { + Some(t) => t, + None => return, + }; + let view = match camera_view_matrix(&game.world) + { + Some(v) => v, + None => return, + }; + + let projection = camera_component.projection_matrix(); + let view_proj = projection * view; + let player_pos = game.world.player_position(); + + let billboard_calls = + dialog_bubble_render_system(&game.world, camera_transform.position, view_proj); + + let frame = render::render( + &view, + &projection, + camera_transform.position, + player_pos, + draw_calls, + &billboard_calls, + time, + delta, + game.world.debug_mode, + ); + + if game.editor.active || game.editor.show_player_state + { + let screen_view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + let mut encoder = render::with_device(|d| { + d.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("ImGui Encoder"), + }) + }); + game.editor.render(&mut encoder, &screen_view); + render::with_queue(|q| q.submit(std::iter::once(encoder.finish()))); + } + + frame.present(); +} + +const FIXED_TIMESTEP: f32 = 1.0 / 60.0; + +fn main() -> Result<(), Box> +{ + let mut game = init()?; + + loop + { + let frame_start = Instant::now(); + let time = Time::get_time_elapsed(); + let delta = (frame_start - game.last_frame).as_secs_f32(); + game.last_frame = frame_start; + + // --- events --- + if process_events(&mut game) + { + break; } - if input_state.f2_just_pressed + if game.input_state.debug_cycle_just_pressed { - editor.show_player_state = !editor.show_player_state; + game.world.debug_mode = game.world.debug_mode.cycle(); + println!("Debug mode: {:?}", game.world.debug_mode); + } + if game.input_state.f2_just_pressed + { + game.editor.show_player_state = !game.editor.show_player_state; } - camera_input_system(&mut world, &input_state); + // --- camera + input --- + camera_input_system(&mut game.world, &game.input_state); - if editor.active + if game.editor.active { - editor_loop(&mut editor, &mut world, &input_state, &stats, delta); + editor_loop( + &mut game.editor, + &mut game.world, + &game.input_state, + &game.stats, + delta, + ); } else { - let dialog_active = !world.bubble_tags.all().is_empty(); + let dialog_active = !game.world.bubble_tags.all().is_empty(); if dialog_active { - dialog_camera_system(&mut world, delta); + dialog_camera_system(&mut game.world, delta); } else { - camera_follow_system(&mut world); + camera_follow_system(&mut game.world); } - player_input_system(&mut world, &input_state); - if editor.show_player_state + player_input_system(&mut game.world, &game.input_state); + if game.editor.show_player_state { - editor.build_hud(&world); + game.editor.build_hud(&game.world); } } + // --- fixed-step physics --- let physics_start = Instant::now(); + game.physics_accumulator += delta; - physics_accumulator += delta; - - while physics_accumulator >= FIXED_TIMESTEP + while game.physics_accumulator >= FIXED_TIMESTEP { - state_machine_physics_system(&mut world, FIXED_TIMESTEP); - + state_machine_physics_system(&mut game.world, FIXED_TIMESTEP); PhysicsManager::physics_step(); - - physics_sync_system(&mut world); - trigger_system(&mut world); - dialog_system(&mut world, FIXED_TIMESTEP); - dialog_projectile_system(&mut world, &input_state); - - physics_accumulator -= FIXED_TIMESTEP; + physics_sync_system(&mut game.world); + trigger_system(&mut game.world); + dialog_system(&mut game.world, FIXED_TIMESTEP); + dialog_projectile_system(&mut game.world, &game.input_state); + game.physics_accumulator -= FIXED_TIMESTEP; } - stats.physics_budget_ms = physics_start.elapsed().as_secs_f32() * 1000.0; + game.stats.physics_budget_ms = physics_start.elapsed().as_secs_f32() * 1000.0; - state_machine_system(&mut world, delta); + // --- per-frame systems --- + state_machine_system(&mut game.world, delta); + rotate_system(&mut game.world, delta); - rotate_system(&mut world, delta); + tree_occlusion_system(&mut game.world); + tree_dissolve_update_system(&mut game.world, delta); + tree_instance_buffer_update_system(&mut game.world); - tree_occlusion_system(&mut world); - tree_dissolve_update_system(&mut world, delta); - tree_instance_buffer_update_system(&mut world); - - let spotlights = spotlight_sync_system(&world); + let spotlights = spotlight_sync_system(&game.world); render::update_spotlights(spotlights); - snow_system(&world, &mut snow_layer, editor.active); + snow_system(&mut game.world, game.editor.active); - let mut draw_calls = render_system(&world); - draw_calls.extend(snow_layer.get_draw_calls()); + // --- draw call collection --- + let mut draw_calls = render_system(&game.world); + if let Some(ref snow_layer) = game.world.snow_layer + { + draw_calls.extend(snow_layer.get_draw_calls()); + } - if debug_mode == DebugMode::Colliders + if game.world.debug_mode == DebugMode::Colliders { draw_calls.extend(collider_debug::render_collider_debug()); } - if let Some((camera_entity, camera_component)) = world.active_camera() - { - if let Some(camera_transform) = world.transforms.get(camera_entity) - { - let player_pos = world.player_position(); + game.stats.draw_call_count = draw_calls.len(); + game.stats.fps = 1.0 / delta; + game.stats.frame_ms = delta * 1000.0; - if let Some(view) = camera_view_matrix(&world) - { - let projection = camera_component.projection_matrix(); - let view_proj = projection * view; + // --- render --- + submit_frame(&mut game, &draw_calls, time, delta); - let billboard_calls = - dialog_bubble_render_system(&world, camera_transform.position, view_proj); - - stats.draw_call_count = draw_calls.len(); - stats.fps = 1.0 / delta; - stats.frame_ms = delta * 1000.0; - - let frame = render::render( - &view, - &projection, - camera_transform.position, - player_pos, - &draw_calls, - &billboard_calls, - time, - delta, - debug_mode, - ); - - if editor.active || editor.show_player_state - { - let screen_view = frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - let mut encoder = render::with_device(|d| { - d.create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("ImGui Encoder"), - }) - }); - editor.render(&mut encoder, &screen_view); - render::with_queue(|q| q.submit(std::iter::once(encoder.finish()))); - } - - frame.present(); - } - } - } - - input_state.clear_just_pressed(); + // --- end frame --- + game.input_state.clear_just_pressed(); let frame_time = frame_start.elapsed(); - if frame_time < frame_duration + if frame_time < game.frame_duration { - std::thread::sleep(frame_duration - frame_time); + std::thread::sleep(game.frame_duration - frame_time); } } diff --git a/src/render/global.rs b/src/render/global.rs new file mode 100644 index 0000000..dda7cb6 --- /dev/null +++ b/src/render/global.rs @@ -0,0 +1,127 @@ +/// Global renderer access via thread-local storage. +/// +/// This module isolates the global singleton pattern used to give systems +/// and loaders access to the wgpu `Device` and `Queue` without threading +/// them through every call site. The long-term goal is to replace these +/// with explicit `RenderContext` parameters on systems that need GPU access. +use std::cell::RefCell; + +use crate::debug::DebugMode; +use crate::entity::EntityHandle; + +use super::{BillboardDrawCall, DrawCall, Renderer, Spotlight}; + +thread_local! { + static GLOBAL_RENDERER: RefCell> = RefCell::new(None); +} + +fn with_ref(f: F) -> R +where + F: FnOnce(&Renderer) -> R, +{ + GLOBAL_RENDERER.with(|r| { + let renderer = r.borrow(); + let renderer = renderer.as_ref().expect("Renderer not initialized"); + f(renderer) + }) +} + +fn with_mut(f: F) -> R +where + F: FnOnce(&mut Renderer) -> R, +{ + GLOBAL_RENDERER.with(|r| { + let mut renderer = r.borrow_mut(); + let renderer = renderer.as_mut().expect("Renderer not initialized"); + f(renderer) + }) +} + +pub fn init(renderer: Renderer) +{ + GLOBAL_RENDERER.with(|r| *r.borrow_mut() = Some(renderer)); +} + +pub fn with_device(f: F) -> R +where + F: FnOnce(&wgpu::Device) -> R, +{ + with_ref(|r| f(&r.device)) +} + +pub fn with_queue(f: F) -> R +where + F: FnOnce(&wgpu::Queue) -> R, +{ + with_ref(|r| f(&r.queue)) +} + +pub fn with_surface_format(f: F) -> R +where + F: FnOnce(wgpu::TextureFormat) -> R, +{ + with_ref(|r| f(r.config.format)) +} + +pub fn aspect_ratio() -> f32 +{ + with_ref(|r| r.aspect_ratio()) +} + +pub fn set_terrain_data() +{ + with_mut(|r| r.set_terrain_data()); +} + +pub fn init_snow_light_accumulation(terrain_min: glam::Vec2, terrain_max: glam::Vec2) +{ + with_mut(|r| r.init_snow_light_accumulation(terrain_min, terrain_max)); +} + +pub fn set_snow_depth(snow_depth_view: &wgpu::TextureView) +{ + with_mut(|r| r.set_snow_depth(snow_depth_view)); +} + +#[allow(dead_code)] +pub fn set_shadow_bias(bias: f32) +{ + with_mut(|r| r.shadow_bias = bias); +} + +pub fn update_spotlights(spotlights: Vec) +{ + with_mut(|r| r.spotlights = spotlights); +} + +pub fn set_selected_entity(entity: Option) +{ + with_mut(|r| r.selected_entity = entity); +} + +pub fn render( + view: &glam::Mat4, + projection: &glam::Mat4, + camera_position: glam::Vec3, + player_position: glam::Vec3, + draw_calls: &[DrawCall], + billboard_calls: &[BillboardDrawCall], + time: f32, + delta_time: f32, + debug_mode: DebugMode, +) -> wgpu::SurfaceTexture +{ + with_mut(|r| { + r.render( + view, + projection, + camera_position, + player_position, + draw_calls, + billboard_calls, + time, + delta_time, + debug_mode, + ) + }) +} diff --git a/src/render/mod.rs b/src/render/mod.rs index b3943e9..7e27324 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,11 +1,19 @@ -pub mod billboard; mod bind_group; mod debug_overlay; +mod global; mod pipeline; mod shadow; mod types; +pub mod billboard; +pub mod snow; +pub mod snow_light; + pub use billboard::{BillboardDrawCall, BillboardPipeline}; +pub use global::{ + aspect_ratio, init, init_snow_light_accumulation, render, set_selected_entity, set_snow_depth, + set_terrain_data, update_spotlights, with_device, with_queue, with_surface_format, +}; pub use types::{DrawCall, Pipeline, Spotlight, SpotlightRaw, Uniforms, MAX_SPOTLIGHTS}; use crate::entity::EntityHandle; @@ -18,7 +26,6 @@ use pipeline::{ create_debug_lines_pipeline, create_main_pipeline, create_snow_clipmap_pipeline, create_wireframe_pipeline, }; -use std::cell::RefCell; use std::num::NonZeroU64; const MAX_DRAW_CALLS: usize = 64; @@ -71,7 +78,7 @@ pub struct Renderer dummy_snow_light_view: wgpu::TextureView, dummy_snow_light_sampler: wgpu::Sampler, - snow_light_accumulation: Option, + snow_light_accumulation: Option, snow_light_bound: bool, pub selected_entity: Option, @@ -1037,12 +1044,8 @@ impl Renderer pub fn init_snow_light_accumulation(&mut self, terrain_min: glam::Vec2, terrain_max: glam::Vec2) { - let snow_light_accumulation = crate::snow_light::SnowLightAccumulation::new( - &self.device, - terrain_min, - terrain_max, - 512, - ); + let snow_light_accumulation = + snow_light::SnowLightAccumulation::new(&self.device, terrain_min, terrain_max, 512); self.snow_light_accumulation = Some(snow_light_accumulation); } @@ -1093,137 +1096,3 @@ impl Renderer self.config.width as f32 / self.config.height as f32 } } - -thread_local! { - static GLOBAL_RENDERER: RefCell> = RefCell::new(None); -} - -pub fn init(renderer: Renderer) -{ - GLOBAL_RENDERER.with(|r| *r.borrow_mut() = Some(renderer)); -} - -pub fn with_device(f: F) -> R -where - F: FnOnce(&wgpu::Device) -> R, -{ - GLOBAL_RENDERER.with(|r| { - let renderer = r.borrow(); - let renderer = renderer.as_ref().expect("Renderer not set"); - f(&renderer.device) - }) -} - -pub fn with_queue(f: F) -> R -where - F: FnOnce(&wgpu::Queue) -> R, -{ - GLOBAL_RENDERER.with(|r| { - let renderer = r.borrow(); - let renderer = renderer.as_ref().expect("Renderer not set"); - f(&renderer.queue) - }) -} - -pub fn set_terrain_data() -{ - GLOBAL_RENDERER.with(|r| { - let mut renderer = r.borrow_mut(); - let renderer = renderer.as_mut().expect("Renderer not set"); - renderer.set_terrain_data(); - }); -} - -pub fn init_snow_light_accumulation(terrain_min: glam::Vec2, terrain_max: glam::Vec2) -{ - GLOBAL_RENDERER.with(|r| { - let mut renderer = r.borrow_mut(); - let renderer = renderer.as_mut().expect("Renderer not set"); - renderer.init_snow_light_accumulation(terrain_min, terrain_max); - }); -} - -pub fn set_snow_depth(snow_depth_view: &wgpu::TextureView) -{ - GLOBAL_RENDERER.with(|r| { - let mut renderer = r.borrow_mut(); - let renderer = renderer.as_mut().expect("Renderer not set"); - renderer.set_snow_depth(snow_depth_view); - }); -} - -pub fn aspect_ratio() -> f32 -{ - GLOBAL_RENDERER.with(|r| { - let renderer = r.borrow(); - let renderer = renderer.as_ref().expect("Renderer not set"); - renderer.aspect_ratio() - }) -} - -pub fn render( - view: &glam::Mat4, - projection: &glam::Mat4, - camera_position: glam::Vec3, - player_position: glam::Vec3, - draw_calls: &[DrawCall], - billboard_calls: &[BillboardDrawCall], - time: f32, - delta_time: f32, - debug_mode: DebugMode, -) -> wgpu::SurfaceTexture -{ - GLOBAL_RENDERER.with(|r| { - let mut renderer = r.borrow_mut(); - let renderer = renderer.as_mut().expect("Renderer not set"); - renderer.render( - view, - projection, - camera_position, - player_position, - draw_calls, - billboard_calls, - time, - delta_time, - debug_mode, - ) - }) -} - -pub fn with_surface_format(f: F) -> R -where - F: FnOnce(wgpu::TextureFormat) -> R, -{ - GLOBAL_RENDERER.with(|r| { - let renderer = r.borrow(); - let renderer = renderer.as_ref().expect("Renderer not set"); - f(renderer.config.format) - }) -} - -pub fn set_shadow_bias(bias: f32) -{ - GLOBAL_RENDERER.with(|r| { - let mut renderer = r.borrow_mut(); - let renderer = renderer.as_mut().expect("Renderer not set"); - renderer.shadow_bias = bias; - }); -} - -pub fn update_spotlights(spotlights: Vec) -{ - GLOBAL_RENDERER.with(|r| { - let mut renderer = r.borrow_mut(); - let renderer = renderer.as_mut().expect("Renderer not set"); - renderer.spotlights = spotlights; - }); -} - -pub fn set_selected_entity(entity: Option) -{ - GLOBAL_RENDERER.with(|r| { - let mut renderer = r.borrow_mut(); - let renderer = renderer.as_mut().expect("Renderer not set"); - renderer.selected_entity = entity; - }); -} diff --git a/src/snow.rs b/src/render/snow.rs similarity index 96% rename from src/snow.rs rename to src/render/snow.rs index dcbf7db..115c70a 100644 --- a/src/snow.rs +++ b/src/render/snow.rs @@ -4,10 +4,10 @@ use exr::prelude::{ReadChannels, ReadLayers}; use glam::{Vec2, Vec3}; use wgpu::util::DeviceExt; +use super::{with_device, with_queue, DrawCall, Pipeline}; use crate::{ loaders::mesh::{InstanceRaw, Mesh, Vertex}, paths, - render::{self, DrawCall, Pipeline}, texture::HeightmapTexture, }; @@ -78,7 +78,7 @@ pub struct SnowLayer fn create_instance_buffer() -> wgpu::Buffer { - render::with_device(|device| { + with_device(|device| { device.create_buffer(&wgpu::BufferDescriptor { label: Some("Snow Clipmap Instance Buffer"), size: std::mem::size_of::() as u64, @@ -107,13 +107,11 @@ impl SnowLayer let (deform_pipeline, deform_bind_group, deform_params_buffer) = Self::create_deform_pipeline(&depth_texture_view); - let heightmap_texture = render::with_device(|device| { - render::with_queue(|queue| { - HeightmapTexture::load(device, queue, &config.heightmap_path) - }) + let heightmap_texture = with_device(|device| { + with_queue(|queue| HeightmapTexture::load(device, queue, &config.heightmap_path)) })?; - let depth_sampler = render::with_device(|device| { + let depth_sampler = with_device(|device| { device.create_sampler(&wgpu::SamplerDescriptor { label: Some("Snow Depth Sampler"), address_mode_u: wgpu::AddressMode::ClampToEdge, @@ -226,7 +224,7 @@ impl SnowLayer height: u32, ) -> (wgpu::Texture, wgpu::TextureView, wgpu::BindGroup) { - render::with_device(|device| { + with_device(|device| { let size = wgpu::Extent3d { width, height, @@ -248,7 +246,7 @@ impl SnowLayer let data_bytes: &[u8] = bytemuck::cast_slice(depth_data); - render::with_queue(|queue| { + with_queue(|queue| { queue.write_texture( wgpu::TexelCopyTextureInfo { texture: &texture, @@ -300,7 +298,7 @@ impl SnowLayer depth_texture_view: &wgpu::TextureView, ) -> (wgpu::ComputePipeline, wgpu::BindGroup, wgpu::Buffer) { - render::with_device(|device| { + with_device(|device| { let shader_source = std::fs::read_to_string(&paths::shaders::snow_deform()) .expect("Failed to load snow deform shader"); @@ -383,7 +381,7 @@ impl SnowLayer depth_sampler: &wgpu::Sampler, ) -> wgpu::BindGroup { - render::with_device(|device| { + with_device(|device| { let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("Snow Displacement Bind Group Layout"), entries: &[ @@ -490,7 +488,7 @@ impl SnowLayer } } - let vertex_buffer = render::with_device(|device| { + let vertex_buffer = with_device(|device| { device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some(&format!("Snow Clipmap Level {} Vertex Buffer", level)), contents: bytemuck::cast_slice(&vertices), @@ -498,7 +496,7 @@ impl SnowLayer }) }); - let index_buffer = render::with_device(|device| { + let index_buffer = with_device(|device| { device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some(&format!("Snow Clipmap Level {} Index Buffer", level)), contents: bytemuck::cast_slice(&indices), @@ -542,7 +540,7 @@ impl SnowLayer } } - render::with_queue(|queue| { + with_queue(|queue| { for (level, clipmap_level) in self.levels.iter().enumerate() { let cell_size = self.clipmap_config.base_cell_size * (1u32 << level) as f32; @@ -589,7 +587,7 @@ impl SnowLayer pub fn deform_at_position(&self, position: Vec3, radius: f32, depth: f32) { - render::with_queue(|queue| { + with_queue(|queue| { let params_data = [ position.x, position.z, @@ -605,7 +603,7 @@ impl SnowLayer queue.write_buffer(&self.deform_params_buffer, 0, params_bytes); }); - render::with_device(|device| { + with_device(|device| { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Snow Deform Encoder"), }); @@ -625,7 +623,7 @@ impl SnowLayer compute_pass.dispatch_workgroups(dispatch_x, dispatch_y, 1); } - render::with_queue(|queue| { + with_queue(|queue| { queue.submit(Some(encoder.finish())); }); }); diff --git a/src/snow_light.rs b/src/render/snow_light.rs similarity index 96% rename from src/snow_light.rs rename to src/render/snow_light.rs index bf256a9..775aa82 100644 --- a/src/snow_light.rs +++ b/src/render/snow_light.rs @@ -1,3 +1,4 @@ +use super::{Spotlight, SpotlightRaw, MAX_SPOTLIGHTS}; use crate::paths; use bytemuck::{Pod, Zeroable}; use glam::Vec2; @@ -13,12 +14,12 @@ struct AccumulationUniforms delta_time: f32, spotlight_count: u32, _padding: u32, - light_view_projections: [[[f32; 4]; 4]; crate::render::MAX_SPOTLIGHTS], + light_view_projections: [[[f32; 4]; 4]; MAX_SPOTLIGHTS], shadow_bias: f32, terrain_height_scale: f32, _padding3: f32, _padding4: f32, - spotlights: [crate::render::SpotlightRaw; crate::render::MAX_SPOTLIGHTS], + spotlights: [SpotlightRaw; MAX_SPOTLIGHTS], } pub struct SnowLightAccumulation @@ -417,7 +418,7 @@ impl SnowLightAccumulation &mut self, encoder: &mut wgpu::CommandEncoder, queue: &wgpu::Queue, - spotlights: &[crate::render::Spotlight], + spotlights: &[Spotlight], delta_time: f32, light_view_projections: &[glam::Mat4], shadow_bias: f32, @@ -430,12 +431,8 @@ impl SnowLightAccumulation self.needs_clear = false; } - let mut spotlight_array = - [crate::render::SpotlightRaw::default(); crate::render::MAX_SPOTLIGHTS]; - for (i, spotlight) in spotlights - .iter() - .take(crate::render::MAX_SPOTLIGHTS) - .enumerate() + let mut spotlight_array = [SpotlightRaw::default(); MAX_SPOTLIGHTS]; + for (i, spotlight) in spotlights.iter().take(MAX_SPOTLIGHTS).enumerate() { spotlight_array[i] = spotlight.to_raw(); } @@ -445,13 +442,13 @@ impl SnowLightAccumulation terrain_max_xz: self.terrain_max.to_array(), decay_rate: self.decay_rate, delta_time, - spotlight_count: spotlights.len().min(crate::render::MAX_SPOTLIGHTS) as u32, + spotlight_count: spotlights.len().min(MAX_SPOTLIGHTS) as u32, _padding: 0, light_view_projections: { - let mut arr = [[[0.0f32; 4]; 4]; crate::render::MAX_SPOTLIGHTS]; + let mut arr = [[[0.0f32; 4]; 4]; MAX_SPOTLIGHTS]; for (i, mat) in light_view_projections .iter() - .take(crate::render::MAX_SPOTLIGHTS) + .take(MAX_SPOTLIGHTS) .enumerate() { arr[i] = mat.to_cols_array_2d(); diff --git a/src/systems/snow.rs b/src/systems/snow.rs index 52ffb88..b838e0d 100644 --- a/src/systems/snow.rs +++ b/src/systems/snow.rs @@ -1,13 +1,15 @@ -use crate::snow::SnowLayer; use crate::world::World; -pub fn snow_system(world: &World, snow_layer: &mut SnowLayer, noclip: bool) +pub fn snow_system(world: &mut World, noclip: bool) { let camera_pos = world.active_camera_position(); let player_pos = world.player_position(); - if !noclip + if let Some(ref mut snow_layer) = world.snow_layer { - snow_layer.deform_at_position(player_pos, 1.5, 10.0); + if !noclip + { + snow_layer.deform_at_position(player_pos, 1.5, 10.0); + } + snow_layer.update(camera_pos); } - snow_layer.update(camera_pos); } diff --git a/src/world.rs b/src/world.rs index 3c324df..8f07722 100644 --- a/src/world.rs +++ b/src/world.rs @@ -15,7 +15,9 @@ use crate::components::{ CameraComponent, InputComponent, JumpComponent, MeshComponent, MovementComponent, PhysicsComponent, RotateComponent, }; +use crate::debug::DebugMode; use crate::entity::{EntityHandle, EntityManager}; +use crate::render::snow::SnowLayer; use crate::state::StateMachine; pub use crate::utility::transform::Transform; @@ -107,6 +109,10 @@ pub struct World pub bubble_tags: Storage<()>, pub projectile_tags: Storage<()>, pub dialog_outcomes: Vec, + + // --- singleton state (not per-entity) --- + pub snow_layer: Option, + pub debug_mode: DebugMode, } impl World @@ -145,6 +151,8 @@ impl World bubble_tags: Storage::new(), projectile_tags: Storage::new(), dialog_outcomes: Vec::new(), + snow_layer: None, + debug_mode: DebugMode::default(), } }