mirror of
https://github.com/eliasstepanik/voxel-simulation.git
synced 2026-01-11 13:58:30 +00:00
Improve chunk queue performance
This commit is contained in:
parent
366381286a
commit
d56b2857d6
@ -6,7 +6,7 @@ use crate::plugins::environment::systems::voxels::queue_systems;
|
|||||||
use crate::plugins::environment::systems::voxels::queue_systems::{enqueue_visible_chunks, process_chunk_queue};
|
use crate::plugins::environment::systems::voxels::queue_systems::{enqueue_visible_chunks, process_chunk_queue};
|
||||||
use crate::plugins::environment::systems::voxels::render_chunks::rebuild_dirty_chunks;
|
use crate::plugins::environment::systems::voxels::render_chunks::rebuild_dirty_chunks;
|
||||||
use crate::plugins::environment::systems::voxels::lod::update_chunk_lods;
|
use crate::plugins::environment::systems::voxels::lod::update_chunk_lods;
|
||||||
use crate::plugins::environment::systems::voxels::structure::{ChunkBudget, ChunkCullingCfg, ChunkQueue, SparseVoxelOctree, SpawnedChunks, ChunkOffsets, PrevCameraChunk};
|
use crate::plugins::environment::systems::voxels::structure::{ChunkBudget, ChunkCullingCfg, ChunkQueue, SparseVoxelOctree, SpawnedChunks, PrevCameraChunk};
|
||||||
|
|
||||||
pub struct EnvironmentPlugin;
|
pub struct EnvironmentPlugin;
|
||||||
impl Plugin for EnvironmentPlugin {
|
impl Plugin for EnvironmentPlugin {
|
||||||
@ -25,7 +25,6 @@ impl Plugin for EnvironmentPlugin {
|
|||||||
let view_distance_chunks = 100;
|
let view_distance_chunks = 100;
|
||||||
app.insert_resource(ChunkCullingCfg { view_distance_chunks });
|
app.insert_resource(ChunkCullingCfg { view_distance_chunks });
|
||||||
app.insert_resource(ChunkBudget { per_frame: 20 });
|
app.insert_resource(ChunkBudget { per_frame: 20 });
|
||||||
app.insert_resource(ChunkOffsets::new(view_distance_chunks));
|
|
||||||
app.init_resource::<PrevCameraChunk>();
|
app.init_resource::<PrevCameraChunk>();
|
||||||
app.add_systems(Update, log_mesh_count);
|
app.add_systems(Update, log_mesh_count);
|
||||||
app
|
app
|
||||||
|
|||||||
@ -11,31 +11,28 @@ pub fn update_chunk_lods(
|
|||||||
cfg: Res<ChunkCullingCfg>,
|
cfg: Res<ChunkCullingCfg>,
|
||||||
) {
|
) {
|
||||||
let cam_pos = cam_q.single().translation();
|
let cam_pos = cam_q.single().translation();
|
||||||
let (max_depth, range_step, chunk_size);
|
|
||||||
{
|
// Borrow the octree only once to avoid repeated query lookups
|
||||||
let tree = tree_q.single();
|
let mut tree = tree_q.single_mut();
|
||||||
max_depth = tree.max_depth;
|
let max_depth = tree.max_depth;
|
||||||
range_step = cfg.view_distance_chunks as f32 / max_depth as f32;
|
let range_step = cfg.view_distance_chunks as f32 / max_depth as f32;
|
||||||
chunk_size = CHUNK_SIZE as f32 * tree.get_spacing_at_depth(max_depth);
|
let chunk_size = CHUNK_SIZE as f32 * tree.get_spacing_at_depth(max_depth);
|
||||||
}
|
|
||||||
|
|
||||||
let mut changed = Vec::new();
|
let mut changed = Vec::new();
|
||||||
for (chunk, mut lod) in chunks.iter_mut() {
|
for (chunk, mut lod) in chunks.iter_mut() {
|
||||||
let tree = tree_q.single();
|
|
||||||
let center = chunk_center_world(&tree, chunk.key);
|
let center = chunk_center_world(&tree, chunk.key);
|
||||||
let dist_chunks = cam_pos.distance(center) / chunk_size;
|
let dist_chunks = cam_pos.distance(center) / chunk_size;
|
||||||
let mut level = (dist_chunks / range_step).floor() as u32;
|
let mut level = (dist_chunks / range_step).floor() as u32;
|
||||||
if level > max_depth { level = max_depth; }
|
if level > max_depth {
|
||||||
|
level = max_depth;
|
||||||
|
}
|
||||||
if lod.0 != level {
|
if lod.0 != level {
|
||||||
lod.0 = level;
|
lod.0 = level;
|
||||||
changed.push(chunk.key);
|
changed.push(chunk.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !changed.is_empty() {
|
for key in changed {
|
||||||
let mut tree = tree_q.single_mut();
|
tree.dirty_chunks.insert(key);
|
||||||
for key in changed {
|
|
||||||
tree.dirty_chunks.insert(key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -310,6 +310,7 @@ pub(crate) fn mesh_chunk(
|
|||||||
// ────────────────────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const N: usize = CHUNK_SIZE as usize;
|
const N: usize = CHUNK_SIZE as usize;
|
||||||
|
const MASK_LEN: usize = N * N;
|
||||||
|
|
||||||
// Safe voxel query that falls back to the octree for out‑of‑chunk requests.
|
// Safe voxel query that falls back to the octree for out‑of‑chunk requests.
|
||||||
let filled = |x: i32, y: i32, z: i32| -> bool {
|
let filled = |x: i32, y: i32, z: i32| -> bool {
|
||||||
@ -327,10 +328,12 @@ pub(crate) fn mesh_chunk(
|
|||||||
// Push a single quad (4 vertices, 6 indices). `base` is the lower‑left
|
// Push a single quad (4 vertices, 6 indices). `base` is the lower‑left
|
||||||
// corner in world space; `u`/`v` are the tangent vectors (length 1); `size`
|
// corner in world space; `u`/`v` are the tangent vectors (length 1); `size`
|
||||||
// is expressed in world units along those axes; `n` is the face normal.
|
// is expressed in world units along those axes; `n` is the face normal.
|
||||||
let mut positions = Vec::<[f32; 3]>::new();
|
// Preallocate vertex buffers for better performance
|
||||||
let mut normals = Vec::<[f32; 3]>::new();
|
let voxel_count = N * N * N;
|
||||||
let mut uvs = Vec::<[f32; 2]>::new();
|
let mut positions = Vec::<[f32; 3]>::with_capacity(voxel_count * 4);
|
||||||
let mut indices = Vec::<u32>::new();
|
let mut normals = Vec::<[f32; 3]>::with_capacity(voxel_count * 4);
|
||||||
|
let mut uvs = Vec::<[f32; 2]>::with_capacity(voxel_count * 4);
|
||||||
|
let mut indices = Vec::<u32>::with_capacity(voxel_count * 6);
|
||||||
|
|
||||||
let mut push_quad = |base: Vec3, size: Vec2, n: Vec3, u: Vec3, v: Vec3| {
|
let mut push_quad = |base: Vec3, size: Vec2, n: Vec3, u: Vec3, v: Vec3| {
|
||||||
let i0 = positions.len() as u32;
|
let i0 = positions.len() as u32;
|
||||||
@ -371,8 +374,10 @@ pub(crate) fn mesh_chunk(
|
|||||||
// the 0…N grid lines (inclusive) because the positive‑side faces of the
|
// the 0…N grid lines (inclusive) because the positive‑side faces of the
|
||||||
// last voxel sit at slice N.
|
// last voxel sit at slice N.
|
||||||
for slice in 0..=N {
|
for slice in 0..=N {
|
||||||
// Build the face mask for this slice.
|
// Build the face mask for this slice using a fixed-size array to
|
||||||
let mut mask = vec![false; N * N];
|
// avoid heap allocations.
|
||||||
|
let mut mask = [false; MASK_LEN];
|
||||||
|
let mut visited = [false; MASK_LEN];
|
||||||
let idx = |u: usize, v: usize| -> usize { u * N + v };
|
let idx = |u: usize, v: usize| -> usize { u * N + v };
|
||||||
|
|
||||||
for u in 0..N {
|
for u in 0..N {
|
||||||
@ -396,7 +401,6 @@ pub(crate) fn mesh_chunk(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Greedy merge the mask into maximal rectangles.
|
// Greedy merge the mask into maximal rectangles.
|
||||||
let mut visited = vec![false; N * N];
|
|
||||||
for u0 in 0..N {
|
for u0 in 0..N {
|
||||||
for v0 in 0..N {
|
for v0 in 0..N {
|
||||||
if !mask[idx(u0, v0)] || visited[idx(u0, v0)] {
|
if !mask[idx(u0, v0)] || visited[idx(u0, v0)] {
|
||||||
|
|||||||
@ -10,7 +10,6 @@ pub fn enqueue_visible_chunks(
|
|||||||
mut queue : ResMut<ChunkQueue>,
|
mut queue : ResMut<ChunkQueue>,
|
||||||
spawned : Res<SpawnedChunks>,
|
spawned : Res<SpawnedChunks>,
|
||||||
mut prev_cam : ResMut<PrevCameraChunk>,
|
mut prev_cam : ResMut<PrevCameraChunk>,
|
||||||
offsets : Res<ChunkOffsets>,
|
|
||||||
cfg : Res<ChunkCullingCfg>,
|
cfg : Res<ChunkCullingCfg>,
|
||||||
cam_q : Query<&GlobalTransform, With<Camera>>,
|
cam_q : Query<&GlobalTransform, With<Camera>>,
|
||||||
tree_q : Query<&SparseVoxelOctree>,
|
tree_q : Query<&SparseVoxelOctree>,
|
||||||
@ -24,12 +23,16 @@ pub fn enqueue_visible_chunks(
|
|||||||
}
|
}
|
||||||
prev_cam.0 = Some(centre);
|
prev_cam.0 = Some(centre);
|
||||||
|
|
||||||
for offset in &offsets.0 {
|
let r = cfg.view_distance_chunks;
|
||||||
let key = ChunkKey(centre.0 + offset.x, centre.1 + offset.y, centre.2 + offset.z);
|
for key in &tree.occupied_chunks {
|
||||||
if spawned.0.contains_key(&key) { continue; }
|
let dx = key.0 - centre.0;
|
||||||
if queue.0.contains(&key) { continue; }
|
let dy = key.1 - centre.1;
|
||||||
if !tree.occupied_chunks.contains(&key) { continue; }
|
let dz = key.2 - centre.2;
|
||||||
queue.0.push_back(key);
|
if dx.abs() > r || dy.abs() > r || dz.abs() > r { continue; }
|
||||||
|
if spawned.0.contains_key(key) { continue; }
|
||||||
|
if queue.set.contains(key) { continue; }
|
||||||
|
queue.keys.push_back(*key);
|
||||||
|
queue.set.insert(*key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,7 +44,8 @@ pub fn process_chunk_queue(
|
|||||||
) {
|
) {
|
||||||
let mut tree = tree_q.single_mut();
|
let mut tree = tree_q.single_mut();
|
||||||
for _ in 0..budget.per_frame {
|
for _ in 0..budget.per_frame {
|
||||||
if let Some(key) = queue.0.pop_front() {
|
if let Some(key) = queue.keys.pop_front() {
|
||||||
|
queue.set.remove(&key);
|
||||||
tree.dirty_chunks.insert(key);
|
tree.dirty_chunks.insert(key);
|
||||||
} else { break; }
|
} else { break; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -118,7 +118,10 @@ impl Default for ChunkBudget {
|
|||||||
|
|
||||||
/// FIFO queue with chunk keys that still need meshing
|
/// FIFO queue with chunk keys that still need meshing
|
||||||
#[derive(Resource, Default)]
|
#[derive(Resource, Default)]
|
||||||
pub struct ChunkQueue(pub VecDeque<ChunkKey>);
|
pub struct ChunkQueue {
|
||||||
|
pub keys: VecDeque<ChunkKey>,
|
||||||
|
pub set: HashSet<ChunkKey>,
|
||||||
|
}
|
||||||
|
|
||||||
/// map “which chunk key already has an entity in the world?”
|
/// map “which chunk key already has an entity in the world?”
|
||||||
#[derive(Resource, Default)]
|
#[derive(Resource, Default)]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user