Merge pull request #11 from eliasstepanik/0ypcsj-codex/optimize-code-for-performance

Improve voxel performance
This commit is contained in:
Elias Stepanik 2025-06-09 12:44:21 +02:00 committed by GitHub
commit a3d759aa59
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 39 additions and 32 deletions

View File

@ -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::render_chunks::rebuild_dirty_chunks;
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;
impl Plugin for EnvironmentPlugin {
@ -25,7 +25,6 @@ impl Plugin for EnvironmentPlugin {
let view_distance_chunks = 100;
app.insert_resource(ChunkCullingCfg { view_distance_chunks });
app.insert_resource(ChunkBudget { per_frame: 20 });
app.insert_resource(ChunkOffsets::new(view_distance_chunks));
app.init_resource::<PrevCameraChunk>();
app.add_systems(Update, log_mesh_count);
app

View File

@ -11,31 +11,28 @@ pub fn update_chunk_lods(
cfg: Res<ChunkCullingCfg>,
) {
let cam_pos = cam_q.single().translation();
let (max_depth, range_step, chunk_size);
{
let tree = tree_q.single();
max_depth = tree.max_depth;
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);
}
// Borrow the octree only once to avoid repeated query lookups
let mut tree = tree_q.single_mut();
let max_depth = tree.max_depth;
let range_step = cfg.view_distance_chunks as f32 / max_depth as f32;
let chunk_size = CHUNK_SIZE as f32 * tree.get_spacing_at_depth(max_depth);
let mut changed = Vec::new();
for (chunk, mut lod) in chunks.iter_mut() {
let tree = tree_q.single();
let center = chunk_center_world(&tree, chunk.key);
let dist_chunks = cam_pos.distance(center) / chunk_size;
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 {
lod.0 = level;
changed.push(chunk.key);
}
}
if !changed.is_empty() {
let mut tree = tree_q.single_mut();
for key in changed {
tree.dirty_chunks.insert(key);
}
for key in changed {
tree.dirty_chunks.insert(key);
}
}

View File

@ -310,6 +310,7 @@ pub(crate) fn mesh_chunk(
// ────────────────────────────────────────────────────────────────────────────
const N: usize = CHUNK_SIZE as usize;
const MASK_LEN: usize = N * N;
// Safe voxel query that falls back to the octree for outofchunk requests.
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 lowerleft
// 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.
let mut positions = Vec::<[f32; 3]>::new();
let mut normals = Vec::<[f32; 3]>::new();
let mut uvs = Vec::<[f32; 2]>::new();
let mut indices = Vec::<u32>::new();
// Preallocate vertex buffers for better performance
let voxel_count = N * N * N;
let mut positions = Vec::<[f32; 3]>::with_capacity(voxel_count * 4);
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 i0 = positions.len() as u32;
@ -371,8 +374,10 @@ pub(crate) fn mesh_chunk(
// the 0…N grid lines (inclusive) because the positiveside faces of the
// last voxel sit at slice N.
for slice in 0..=N {
// Build the face mask for this slice.
let mut mask = vec![false; N * N];
// Build the face mask for this slice using a fixed-size array to
// 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 };
for u in 0..N {
@ -396,7 +401,6 @@ pub(crate) fn mesh_chunk(
}
// Greedy merge the mask into maximal rectangles.
let mut visited = vec![false; N * N];
for u0 in 0..N {
for v0 in 0..N {
if !mask[idx(u0, v0)] || visited[idx(u0, v0)] {

View File

@ -10,7 +10,6 @@ pub fn enqueue_visible_chunks(
mut queue : ResMut<ChunkQueue>,
spawned : Res<SpawnedChunks>,
mut prev_cam : ResMut<PrevCameraChunk>,
offsets : Res<ChunkOffsets>,
cfg : Res<ChunkCullingCfg>,
cam_q : Query<&GlobalTransform, With<Camera>>,
tree_q : Query<&SparseVoxelOctree>,
@ -24,12 +23,16 @@ pub fn enqueue_visible_chunks(
}
prev_cam.0 = Some(centre);
for offset in &offsets.0 {
let key = ChunkKey(centre.0 + offset.x, centre.1 + offset.y, centre.2 + offset.z);
if spawned.0.contains_key(&key) { continue; }
if queue.0.contains(&key) { continue; }
if !tree.occupied_chunks.contains(&key) { continue; }
queue.0.push_back(key);
let r = cfg.view_distance_chunks;
for key in &tree.occupied_chunks {
let dx = key.0 - centre.0;
let dy = key.1 - centre.1;
let dz = key.2 - centre.2;
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();
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);
} else { break; }
}

View File

@ -118,7 +118,10 @@ impl Default for ChunkBudget {
/// FIFO queue with chunk keys that still need meshing
#[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?”
#[derive(Resource, Default)]