This commit is contained in:
Jonas H
2026-03-28 11:16:06 +01:00
parent a79c824540
commit e6c8c259e7
7 changed files with 489 additions and 415 deletions

View File

@@ -9,37 +9,24 @@ mod physics;
mod picking; mod picking;
mod postprocess; mod postprocess;
mod render; mod render;
mod snow;
mod snow_light;
mod state; mod state;
mod systems; mod systems;
mod texture; mod texture;
mod utility; mod utility;
mod world; 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::camera::CameraBundle;
use crate::bundles::player::PlayerBundle; use crate::bundles::player::PlayerBundle;
use crate::bundles::spotlight::spawn_spotlights; use crate::bundles::spotlight::spawn_spotlights;
use crate::bundles::terrain::{TerrainBundle, TerrainConfig}; use crate::bundles::terrain::{TerrainBundle, TerrainConfig};
use crate::bundles::test_char::TestCharBundle; use crate::bundles::test_char::TestCharBundle;
use crate::bundles::Bundle; 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::loaders::scene::Space;
use crate::physics::PhysicsManager; 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::stop_camera_following;
use crate::systems::{ use crate::systems::{
camera_follow_system, camera_input_system, camera_view_matrix, dialog_bubble_render_system, 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, tree_dissolve_update_system, tree_instance_buffer_update_system, tree_occlusion_system,
trigger_system, trigger_system,
}; };
use crate::utility::input::InputState;
use crate::utility::time::Time; use crate::utility::time::Time;
fn main() -> Result<(), Box<dyn std::error::Error>> 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<Game, Box<dyn std::error::Error>>
{ {
let sdl_context = sdl3::init()?; let sdl_context = sdl3::init()?;
let video_subsystem = sdl_context.video()?; let video_subsystem = sdl_context.video()?;
@@ -72,34 +84,69 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
}); });
editor.init_platform(&window); 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<dyn std::error::Error>>
{
let space = Space::load_space(&crate::paths::meshes::terrain())?; let space = Space::load_space(&crate::paths::meshes::terrain())?;
let terrain_config = TerrainConfig::default(); let terrain_config = TerrainConfig::default();
let player_spawn = space.player_spawn;
let camera_spawn = space.camera_spawn_position();
let tree_positions: Vec<Vec3> = space let tree_positions: Vec<Vec3> = space
.mesh_data .mesh_data
.iter() .iter()
.flat_map(|(_, instances)| instances.iter().map(|inst| inst.position)) .flat_map(|(_, instances)| instances.iter().map(|inst| inst.position))
.collect(); .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 mut world = World::new();
let _player_entity = PlayerBundle { PlayerBundle {
position: player_spawn, position: player_spawn,
} }
.spawn(&mut world) .spawn(&mut world)
.unwrap(); .unwrap();
let _test_char_entity = TestCharBundle { TestCharBundle {
position: space.test_char_spawn, position: test_char_spawn,
} }
.spawn(&mut world) .spawn(&mut world)
.unwrap(); .unwrap();
let _terrain_entity = TerrainBundle::spawn(&mut world, space.mesh_data, &terrain_config)?; TerrainBundle::spawn(&mut world, mesh_data, &terrain_config)?;
spawn_spotlights(&mut world, space.spotlights); spawn_spotlights(&mut world, spotlights);
render::set_terrain_data(); render::set_terrain_data();
@@ -110,64 +157,38 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
); );
let snow_config = SnowConfig::default(); 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 for pos in &tree_positions
{ {
snow_layer.deform_at_position(*pos, 5.0, 50.0); snow_layer.deform_at_position(*pos, 5.0, 50.0);
} }
println!("Snow layer loaded successfully"); println!("Snow layer loaded successfully");
render::set_snow_depth(&snow_layer.depth_texture_view); render::set_snow_depth(&snow_layer.depth_texture_view);
world.snow_layer = Some(snow_layer);
let mut debug_mode = DebugMode::default();
let camera_entity = CameraBundle { let camera_entity = CameraBundle {
position: camera_spawn, position: camera_spawn,
} }
.spawn(&mut world) .spawn(&mut world)
.unwrap(); .unwrap();
start_camera_following(&mut world, camera_entity);
let _event_pump = sdl_context.event_pump()?; Ok((world, camera_entity))
let mut input_state = InputState::new(); }
sdl_context.mouse().set_relative_mouse_mode(&window, true); fn process_events(game: &mut Game) -> bool
{
Time::init(); game.editor.begin_frame();
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
{
let frame_start = Instant::now();
let time = Time::get_time_elapsed();
let delta = (frame_start - last_frame).as_secs_f32();
last_frame = frame_start;
editor.begin_frame();
while let Some(raw_event) = dear_imgui_sdl3::sdl3_poll_event_ll() while let Some(raw_event) = dear_imgui_sdl3::sdl3_poll_event_ll()
{ {
editor.process_event(&raw_event); game.editor.process_event(&raw_event);
let event = Event::from_ll(raw_event); let event = Event::from_ll(raw_event);
match &event match &event
{ {
Event::Quit { .. } => Event::Quit { .. } =>
{ {
input_state.quit_requested = true; return true;
continue;
} }
Event::KeyDown { Event::KeyDown {
@@ -176,43 +197,34 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
.. ..
} => } =>
{ {
editor.active = !editor.active; toggle_editor(game);
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; continue;
} }
Event::MouseButtonDown { Event::MouseButtonDown {
mouse_btn: MouseButton::Right, mouse_btn: MouseButton::Right,
.. ..
} if editor.active => } if game.editor.active =>
{ {
editor.right_mouse_held = true; game.editor.right_mouse_held = true;
input_state.mouse_captured = true; game.input_state.mouse_captured = true;
stop_camera_following(&mut world, camera_entity); stop_camera_following(&mut game.world, game.camera_entity);
sdl_context.mouse().set_relative_mouse_mode(&window, true); game.sdl_context
.mouse()
.set_relative_mouse_mode(&game.window, true);
continue; continue;
} }
Event::MouseButtonUp { Event::MouseButtonUp {
mouse_btn: MouseButton::Right, mouse_btn: MouseButton::Right,
.. ..
} if editor.active => } if game.editor.active =>
{ {
editor.right_mouse_held = false; game.editor.right_mouse_held = false;
input_state.mouse_captured = false; game.input_state.mouse_captured = false;
sdl_context.mouse().set_relative_mouse_mode(&window, false); game.sdl_context
.mouse()
.set_relative_mouse_mode(&game.window, false);
continue; continue;
} }
@@ -221,26 +233,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
x, x,
y, y,
.. ..
} if editor.active && !editor.wants_mouse() => } if game.editor.active && !game.editor.wants_mouse() =>
{ {
if let Some(view) = crate::systems::camera_view_matrix(&world) handle_editor_pick(game, *x, *y);
{
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; continue;
} }
@@ -248,132 +243,102 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
{} {}
} }
if editor.active && (editor.wants_keyboard() || editor.wants_mouse()) if game.editor.active && (game.editor.wants_keyboard() || game.editor.wants_mouse())
{ {
continue; continue;
} }
let capture_changed = input_state.handle_event(&event); let capture_changed = game.input_state.handle_event(&event);
if capture_changed && !editor.active if capture_changed && !game.editor.active
{ {
sdl_context game.sdl_context
.mouse() .mouse()
.set_relative_mouse_mode(&window, input_state.mouse_captured); .set_relative_mouse_mode(&game.window, game.input_state.mouse_captured);
} }
} }
if input_state.quit_requested false
}
fn toggle_editor(game: &mut Game)
{
game.editor.active = !game.editor.active;
if game.editor.active
{ {
break 'running; stop_camera_following(&mut game.world, game.camera_entity);
} game.sdl_context
.mouse()
if input_state.debug_cycle_just_pressed .set_relative_mouse_mode(&game.window, false);
{ game.editor.right_mouse_held = false;
debug_mode = debug_mode.cycle(); game.input_state.mouse_captured = false;
println!("Debug mode: {:?}", debug_mode);
}
if input_state.f2_just_pressed
{
editor.show_player_state = !editor.show_player_state;
}
camera_input_system(&mut world, &input_state);
if editor.active
{
editor_loop(&mut editor, &mut world, &input_state, &stats, delta);
} }
else else
{ {
let dialog_active = !world.bubble_tags.all().is_empty(); start_camera_following(&mut game.world, game.camera_entity);
if dialog_active game.input_state.mouse_captured = true;
{ game.sdl_context
dialog_camera_system(&mut world, delta); .mouse()
.set_relative_mouse_mode(&game.window, true);
} }
else }
fn handle_editor_pick(game: &mut Game, x: f32, y: f32)
{
let view = match camera_view_matrix(&game.world)
{ {
camera_follow_system(&mut world); Some(v) => v,
} None => return,
player_input_system(&mut world, &input_state); };
if editor.show_player_state let (_, cam) = match game.world.active_camera()
{ {
editor.build_hud(&world); 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);
}
let physics_start = Instant::now(); fn submit_frame(game: &mut Game, draw_calls: &[render::DrawCall], time: f32, delta: f32)
{
physics_accumulator += delta; let (camera_entity, camera_component) = match game.world.active_camera()
while physics_accumulator >= FIXED_TIMESTEP
{ {
state_machine_physics_system(&mut world, FIXED_TIMESTEP); Some(c) => c,
None => return,
PhysicsManager::physics_step(); };
let camera_transform = match game.world.transforms.get(camera_entity)
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;
}
stats.physics_budget_ms = physics_start.elapsed().as_secs_f32() * 1000.0;
state_machine_system(&mut world, delta);
rotate_system(&mut world, delta);
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);
render::update_spotlights(spotlights);
snow_system(&world, &mut snow_layer, editor.active);
let mut draw_calls = render_system(&world);
draw_calls.extend(snow_layer.get_draw_calls());
if debug_mode == DebugMode::Colliders
{ {
draw_calls.extend(collider_debug::render_collider_debug()); Some(t) => t,
} None => return,
};
let view = match camera_view_matrix(&game.world)
{
Some(v) => v,
None => return,
};
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();
if let Some(view) = camera_view_matrix(&world)
{
let projection = camera_component.projection_matrix(); let projection = camera_component.projection_matrix();
let view_proj = projection * view; let view_proj = projection * view;
let player_pos = game.world.player_position();
let billboard_calls = let billboard_calls =
dialog_bubble_render_system(&world, camera_transform.position, view_proj); dialog_bubble_render_system(&game.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( let frame = render::render(
&view, &view,
&projection, &projection,
camera_transform.position, camera_transform.position,
player_pos, player_pos,
&draw_calls, draw_calls,
&billboard_calls, &billboard_calls,
time, time,
delta, delta,
debug_mode, game.world.debug_mode,
); );
if editor.active || editor.show_player_state if game.editor.active || game.editor.show_player_state
{ {
let screen_view = frame let screen_view = frame
.texture .texture
@@ -383,21 +348,129 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
label: Some("ImGui Encoder"), label: Some("ImGui Encoder"),
}) })
}); });
editor.render(&mut encoder, &screen_view); game.editor.render(&mut encoder, &screen_view);
render::with_queue(|q| q.submit(std::iter::once(encoder.finish()))); render::with_queue(|q| q.submit(std::iter::once(encoder.finish())));
} }
frame.present(); frame.present();
}
const FIXED_TIMESTEP: f32 = 1.0 / 60.0;
fn main() -> Result<(), Box<dyn std::error::Error>>
{
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 game.input_state.debug_cycle_just_pressed
{
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 ---
camera_input_system(&mut game.world, &game.input_state);
if game.editor.active
{
editor_loop(
&mut game.editor,
&mut game.world,
&game.input_state,
&game.stats,
delta,
);
}
else
{
let dialog_active = !game.world.bubble_tags.all().is_empty();
if dialog_active
{
dialog_camera_system(&mut game.world, delta);
}
else
{
camera_follow_system(&mut game.world);
}
player_input_system(&mut game.world, &game.input_state);
if game.editor.show_player_state
{
game.editor.build_hud(&game.world);
} }
} }
input_state.clear_just_pressed(); // --- fixed-step physics ---
let physics_start = Instant::now();
game.physics_accumulator += delta;
while game.physics_accumulator >= FIXED_TIMESTEP
{
state_machine_physics_system(&mut game.world, FIXED_TIMESTEP);
PhysicsManager::physics_step();
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;
}
game.stats.physics_budget_ms = physics_start.elapsed().as_secs_f32() * 1000.0;
// --- per-frame systems ---
state_machine_system(&mut game.world, delta);
rotate_system(&mut game.world, delta);
tree_occlusion_system(&mut game.world);
tree_dissolve_update_system(&mut game.world, delta);
tree_instance_buffer_update_system(&mut game.world);
let spotlights = spotlight_sync_system(&game.world);
render::update_spotlights(spotlights);
snow_system(&mut game.world, game.editor.active);
// --- 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 game.world.debug_mode == DebugMode::Colliders
{
draw_calls.extend(collider_debug::render_collider_debug());
}
game.stats.draw_call_count = draw_calls.len();
game.stats.fps = 1.0 / delta;
game.stats.frame_ms = delta * 1000.0;
// --- render ---
submit_frame(&mut game, &draw_calls, time, delta);
// --- end frame ---
game.input_state.clear_just_pressed();
let frame_time = frame_start.elapsed(); 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);
} }
} }

127
src/render/global.rs Normal file
View File

@@ -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<Option<Renderer>> = RefCell::new(None);
}
fn with_ref<F, R>(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, R>(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, R>(f: F) -> R
where
F: FnOnce(&wgpu::Device) -> R,
{
with_ref(|r| f(&r.device))
}
pub fn with_queue<F, R>(f: F) -> R
where
F: FnOnce(&wgpu::Queue) -> R,
{
with_ref(|r| f(&r.queue))
}
pub fn with_surface_format<F, R>(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<Spotlight>)
{
with_mut(|r| r.spotlights = spotlights);
}
pub fn set_selected_entity(entity: Option<EntityHandle>)
{
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,
)
})
}

View File

@@ -1,11 +1,19 @@
pub mod billboard;
mod bind_group; mod bind_group;
mod debug_overlay; mod debug_overlay;
mod global;
mod pipeline; mod pipeline;
mod shadow; mod shadow;
mod types; mod types;
pub mod billboard;
pub mod snow;
pub mod snow_light;
pub use billboard::{BillboardDrawCall, BillboardPipeline}; 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}; pub use types::{DrawCall, Pipeline, Spotlight, SpotlightRaw, Uniforms, MAX_SPOTLIGHTS};
use crate::entity::EntityHandle; use crate::entity::EntityHandle;
@@ -18,7 +26,6 @@ use pipeline::{
create_debug_lines_pipeline, create_main_pipeline, create_snow_clipmap_pipeline, create_debug_lines_pipeline, create_main_pipeline, create_snow_clipmap_pipeline,
create_wireframe_pipeline, create_wireframe_pipeline,
}; };
use std::cell::RefCell;
use std::num::NonZeroU64; use std::num::NonZeroU64;
const MAX_DRAW_CALLS: usize = 64; const MAX_DRAW_CALLS: usize = 64;
@@ -71,7 +78,7 @@ pub struct Renderer
dummy_snow_light_view: wgpu::TextureView, dummy_snow_light_view: wgpu::TextureView,
dummy_snow_light_sampler: wgpu::Sampler, dummy_snow_light_sampler: wgpu::Sampler,
snow_light_accumulation: Option<crate::snow_light::SnowLightAccumulation>, snow_light_accumulation: Option<snow_light::SnowLightAccumulation>,
snow_light_bound: bool, snow_light_bound: bool,
pub selected_entity: Option<EntityHandle>, pub selected_entity: Option<EntityHandle>,
@@ -1037,12 +1044,8 @@ impl Renderer
pub fn init_snow_light_accumulation(&mut self, terrain_min: glam::Vec2, terrain_max: glam::Vec2) 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( let snow_light_accumulation =
&self.device, snow_light::SnowLightAccumulation::new(&self.device, terrain_min, terrain_max, 512);
terrain_min,
terrain_max,
512,
);
self.snow_light_accumulation = Some(snow_light_accumulation); self.snow_light_accumulation = Some(snow_light_accumulation);
} }
@@ -1093,137 +1096,3 @@ impl Renderer
self.config.width as f32 / self.config.height as f32 self.config.width as f32 / self.config.height as f32
} }
} }
thread_local! {
static GLOBAL_RENDERER: RefCell<Option<Renderer>> = RefCell::new(None);
}
pub fn init(renderer: Renderer)
{
GLOBAL_RENDERER.with(|r| *r.borrow_mut() = Some(renderer));
}
pub fn with_device<F, R>(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, R>(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, R>(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<Spotlight>)
{
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<EntityHandle>)
{
GLOBAL_RENDERER.with(|r| {
let mut renderer = r.borrow_mut();
let renderer = renderer.as_mut().expect("Renderer not set");
renderer.selected_entity = entity;
});
}

View File

@@ -4,10 +4,10 @@ use exr::prelude::{ReadChannels, ReadLayers};
use glam::{Vec2, Vec3}; use glam::{Vec2, Vec3};
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use super::{with_device, with_queue, DrawCall, Pipeline};
use crate::{ use crate::{
loaders::mesh::{InstanceRaw, Mesh, Vertex}, loaders::mesh::{InstanceRaw, Mesh, Vertex},
paths, paths,
render::{self, DrawCall, Pipeline},
texture::HeightmapTexture, texture::HeightmapTexture,
}; };
@@ -78,7 +78,7 @@ pub struct SnowLayer
fn create_instance_buffer() -> wgpu::Buffer fn create_instance_buffer() -> wgpu::Buffer
{ {
render::with_device(|device| { with_device(|device| {
device.create_buffer(&wgpu::BufferDescriptor { device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Snow Clipmap Instance Buffer"), label: Some("Snow Clipmap Instance Buffer"),
size: std::mem::size_of::<InstanceRaw>() as u64, size: std::mem::size_of::<InstanceRaw>() as u64,
@@ -107,13 +107,11 @@ impl SnowLayer
let (deform_pipeline, deform_bind_group, deform_params_buffer) = let (deform_pipeline, deform_bind_group, deform_params_buffer) =
Self::create_deform_pipeline(&depth_texture_view); Self::create_deform_pipeline(&depth_texture_view);
let heightmap_texture = render::with_device(|device| { let heightmap_texture = with_device(|device| {
render::with_queue(|queue| { with_queue(|queue| HeightmapTexture::load(device, queue, &config.heightmap_path))
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 { device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Snow Depth Sampler"), label: Some("Snow Depth Sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_u: wgpu::AddressMode::ClampToEdge,
@@ -226,7 +224,7 @@ impl SnowLayer
height: u32, height: u32,
) -> (wgpu::Texture, wgpu::TextureView, wgpu::BindGroup) ) -> (wgpu::Texture, wgpu::TextureView, wgpu::BindGroup)
{ {
render::with_device(|device| { with_device(|device| {
let size = wgpu::Extent3d { let size = wgpu::Extent3d {
width, width,
height, height,
@@ -248,7 +246,7 @@ impl SnowLayer
let data_bytes: &[u8] = bytemuck::cast_slice(depth_data); let data_bytes: &[u8] = bytemuck::cast_slice(depth_data);
render::with_queue(|queue| { with_queue(|queue| {
queue.write_texture( queue.write_texture(
wgpu::TexelCopyTextureInfo { wgpu::TexelCopyTextureInfo {
texture: &texture, texture: &texture,
@@ -300,7 +298,7 @@ impl SnowLayer
depth_texture_view: &wgpu::TextureView, depth_texture_view: &wgpu::TextureView,
) -> (wgpu::ComputePipeline, wgpu::BindGroup, wgpu::Buffer) ) -> (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()) let shader_source = std::fs::read_to_string(&paths::shaders::snow_deform())
.expect("Failed to load snow deform shader"); .expect("Failed to load snow deform shader");
@@ -383,7 +381,7 @@ impl SnowLayer
depth_sampler: &wgpu::Sampler, depth_sampler: &wgpu::Sampler,
) -> wgpu::BindGroup ) -> wgpu::BindGroup
{ {
render::with_device(|device| { with_device(|device| {
let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Snow Displacement Bind Group Layout"), label: Some("Snow Displacement Bind Group Layout"),
entries: &[ 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 { device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("Snow Clipmap Level {} Vertex Buffer", level)), label: Some(&format!("Snow Clipmap Level {} Vertex Buffer", level)),
contents: bytemuck::cast_slice(&vertices), 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 { device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("Snow Clipmap Level {} Index Buffer", level)), label: Some(&format!("Snow Clipmap Level {} Index Buffer", level)),
contents: bytemuck::cast_slice(&indices), 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() for (level, clipmap_level) in self.levels.iter().enumerate()
{ {
let cell_size = self.clipmap_config.base_cell_size * (1u32 << level) as f32; 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) pub fn deform_at_position(&self, position: Vec3, radius: f32, depth: f32)
{ {
render::with_queue(|queue| { with_queue(|queue| {
let params_data = [ let params_data = [
position.x, position.x,
position.z, position.z,
@@ -605,7 +603,7 @@ impl SnowLayer
queue.write_buffer(&self.deform_params_buffer, 0, params_bytes); 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 { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Snow Deform Encoder"), label: Some("Snow Deform Encoder"),
}); });
@@ -625,7 +623,7 @@ impl SnowLayer
compute_pass.dispatch_workgroups(dispatch_x, dispatch_y, 1); compute_pass.dispatch_workgroups(dispatch_x, dispatch_y, 1);
} }
render::with_queue(|queue| { with_queue(|queue| {
queue.submit(Some(encoder.finish())); queue.submit(Some(encoder.finish()));
}); });
}); });

View File

@@ -1,3 +1,4 @@
use super::{Spotlight, SpotlightRaw, MAX_SPOTLIGHTS};
use crate::paths; use crate::paths;
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use glam::Vec2; use glam::Vec2;
@@ -13,12 +14,12 @@ struct AccumulationUniforms
delta_time: f32, delta_time: f32,
spotlight_count: u32, spotlight_count: u32,
_padding: u32, _padding: u32,
light_view_projections: [[[f32; 4]; 4]; crate::render::MAX_SPOTLIGHTS], light_view_projections: [[[f32; 4]; 4]; MAX_SPOTLIGHTS],
shadow_bias: f32, shadow_bias: f32,
terrain_height_scale: f32, terrain_height_scale: f32,
_padding3: f32, _padding3: f32,
_padding4: f32, _padding4: f32,
spotlights: [crate::render::SpotlightRaw; crate::render::MAX_SPOTLIGHTS], spotlights: [SpotlightRaw; MAX_SPOTLIGHTS],
} }
pub struct SnowLightAccumulation pub struct SnowLightAccumulation
@@ -417,7 +418,7 @@ impl SnowLightAccumulation
&mut self, &mut self,
encoder: &mut wgpu::CommandEncoder, encoder: &mut wgpu::CommandEncoder,
queue: &wgpu::Queue, queue: &wgpu::Queue,
spotlights: &[crate::render::Spotlight], spotlights: &[Spotlight],
delta_time: f32, delta_time: f32,
light_view_projections: &[glam::Mat4], light_view_projections: &[glam::Mat4],
shadow_bias: f32, shadow_bias: f32,
@@ -430,12 +431,8 @@ impl SnowLightAccumulation
self.needs_clear = false; self.needs_clear = false;
} }
let mut spotlight_array = let mut spotlight_array = [SpotlightRaw::default(); MAX_SPOTLIGHTS];
[crate::render::SpotlightRaw::default(); crate::render::MAX_SPOTLIGHTS]; for (i, spotlight) in spotlights.iter().take(MAX_SPOTLIGHTS).enumerate()
for (i, spotlight) in spotlights
.iter()
.take(crate::render::MAX_SPOTLIGHTS)
.enumerate()
{ {
spotlight_array[i] = spotlight.to_raw(); spotlight_array[i] = spotlight.to_raw();
} }
@@ -445,13 +442,13 @@ impl SnowLightAccumulation
terrain_max_xz: self.terrain_max.to_array(), terrain_max_xz: self.terrain_max.to_array(),
decay_rate: self.decay_rate, decay_rate: self.decay_rate,
delta_time, delta_time,
spotlight_count: spotlights.len().min(crate::render::MAX_SPOTLIGHTS) as u32, spotlight_count: spotlights.len().min(MAX_SPOTLIGHTS) as u32,
_padding: 0, _padding: 0,
light_view_projections: { 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 for (i, mat) in light_view_projections
.iter() .iter()
.take(crate::render::MAX_SPOTLIGHTS) .take(MAX_SPOTLIGHTS)
.enumerate() .enumerate()
{ {
arr[i] = mat.to_cols_array_2d(); arr[i] = mat.to_cols_array_2d();

View File

@@ -1,13 +1,15 @@
use crate::snow::SnowLayer;
use crate::world::World; 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 camera_pos = world.active_camera_position();
let player_pos = world.player_position(); let player_pos = world.player_position();
if let Some(ref mut snow_layer) = world.snow_layer
{
if !noclip if !noclip
{ {
snow_layer.deform_at_position(player_pos, 1.5, 10.0); snow_layer.deform_at_position(player_pos, 1.5, 10.0);
} }
snow_layer.update(camera_pos); snow_layer.update(camera_pos);
}
} }

View File

@@ -15,7 +15,9 @@ use crate::components::{
CameraComponent, InputComponent, JumpComponent, MeshComponent, MovementComponent, CameraComponent, InputComponent, JumpComponent, MeshComponent, MovementComponent,
PhysicsComponent, RotateComponent, PhysicsComponent, RotateComponent,
}; };
use crate::debug::DebugMode;
use crate::entity::{EntityHandle, EntityManager}; use crate::entity::{EntityHandle, EntityManager};
use crate::render::snow::SnowLayer;
use crate::state::StateMachine; use crate::state::StateMachine;
pub use crate::utility::transform::Transform; pub use crate::utility::transform::Transform;
@@ -107,6 +109,10 @@ pub struct World
pub bubble_tags: Storage<()>, pub bubble_tags: Storage<()>,
pub projectile_tags: Storage<()>, pub projectile_tags: Storage<()>,
pub dialog_outcomes: Vec<DialogOutcomeEvent>, pub dialog_outcomes: Vec<DialogOutcomeEvent>,
// --- singleton state (not per-entity) ---
pub snow_layer: Option<SnowLayer>,
pub debug_mode: DebugMode,
} }
impl World impl World
@@ -145,6 +151,8 @@ impl World
bubble_tags: Storage::new(), bubble_tags: Storage::new(),
projectile_tags: Storage::new(), projectile_tags: Storage::new(),
dialog_outcomes: Vec::new(), dialog_outcomes: Vec::new(),
snow_layer: None,
debug_mode: DebugMode::default(),
} }
} }