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 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<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 video_subsystem = sdl_context.video()?;
@@ -72,34 +84,69 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
});
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 terrain_config = TerrainConfig::default();
let player_spawn = space.player_spawn;
let camera_spawn = space.camera_spawn_position();
let tree_positions: Vec<Vec3> = 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,64 +157,38 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
);
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);
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
fn process_events(game: &mut Game) -> bool
{
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();
game.editor.begin_frame();
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);
match &event
{
Event::Quit { .. } =>
{
input_state.quit_requested = true;
continue;
return true;
}
Event::KeyDown {
@@ -176,43 +197,34 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
..
} =>
{
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);
}
toggle_editor(game);
continue;
}
Event::MouseButtonDown {
mouse_btn: MouseButton::Right,
..
} if editor.active =>
} if game.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);
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(&game.window, true);
continue;
}
Event::MouseButtonUp {
mouse_btn: MouseButton::Right,
..
} if editor.active =>
} if game.editor.active =>
{
editor.right_mouse_held = false;
input_state.mouse_captured = false;
sdl_context.mouse().set_relative_mouse_mode(&window, false);
game.editor.right_mouse_held = false;
game.input_state.mouse_captured = false;
game.sdl_context
.mouse()
.set_relative_mouse_mode(&game.window, false);
continue;
}
@@ -221,26 +233,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
x,
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)
{
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);
}
}
handle_editor_pick(game, *x, *y);
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;
}
let capture_changed = input_state.handle_event(&event);
if capture_changed && !editor.active
let capture_changed = game.input_state.handle_event(&event);
if capture_changed && !game.editor.active
{
sdl_context
game.sdl_context
.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)
{
break 'running;
}
if input_state.debug_cycle_just_pressed
game.editor.active = !game.editor.active;
if game.editor.active
{
debug_mode = debug_mode.cycle();
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);
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
{
let dialog_active = !world.bubble_tags.all().is_empty();
if dialog_active
{
dialog_camera_system(&mut world, delta);
}
else
{
camera_follow_system(&mut world);
}
player_input_system(&mut world, &input_state);
if editor.show_player_state
{
editor.build_hud(&world);
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);
}
}
let physics_start = Instant::now();
physics_accumulator += delta;
while physics_accumulator >= FIXED_TIMESTEP
fn handle_editor_pick(game: &mut Game, x: f32, y: f32)
{
state_machine_physics_system(&mut 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;
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);
}
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
fn submit_frame(game: &mut Game, draw_calls: &[render::DrawCall], time: f32, delta: f32)
{
draw_calls.extend(collider_debug::render_collider_debug());
}
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,
};
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 view_proj = projection * view;
let player_pos = game.world.player_position();
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;
dialog_bubble_render_system(&game.world, camera_transform.position, view_proj);
let frame = render::render(
&view,
&projection,
camera_transform.position,
player_pos,
&draw_calls,
draw_calls,
&billboard_calls,
time,
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
.texture
@@ -383,21 +348,129 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
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())));
}
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();
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 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<crate::snow_light::SnowLightAccumulation>,
snow_light_accumulation: Option<snow_light::SnowLightAccumulation>,
snow_light_bound: bool,
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)
{
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<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 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::<InstanceRaw>() 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()));
});
});

View File

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

View File

@@ -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 let Some(ref mut snow_layer) = world.snow_layer
{
if !noclip
{
snow_layer.deform_at_position(player_pos, 1.5, 10.0);
}
snow_layer.update(camera_pos);
}
}

View File

@@ -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<DialogOutcomeEvent>,
// --- singleton state (not per-entity) ---
pub snow_layer: Option<SnowLayer>,
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(),
}
}