use crate::ecs::mesh::components::{
Vertex, create_cone_mesh, create_cube_mesh, create_cylinder_mesh, create_plane_mesh,
create_sphere_mesh, create_subdivided_plane_mesh, create_torus_mesh,
};
use crate::ecs::prefab::resources::mesh_cache_iter;
use super::super::types::{
BUFFER_GROWTH_FACTOR, BUFFER_SHRINK_THRESHOLD, DrawIndexedIndirect, MAX_INSTANCES, ModelMatrix,
compute_normal_matrix,
};
use super::super::world_state::BatchRange;
use super::MeshPass;
impl MeshPass {
pub(in super::super) fn can_do_rebatch_only(&self) -> bool {
let Some(fd) = self.frame_dirty.as_ref() else {
return false;
};
if !fd.frame_initialized
|| fd.full_rebuild_needed
|| fd.instanced_meshes_changed
|| !fd.entities_added.is_empty()
|| !fd.entities_removed.is_empty()
|| !fd.material_dirty.is_empty()
{
return false;
}
if !fd.batches_invalidated {
return false;
}
let world_state = match self.world_states.get(&self.current_world_id) {
Some(state) => state,
None => return false,
};
!world_state.cached_entities.is_empty() && world_state.gpu_buffers.is_some()
}
pub(in super::super) fn rebatch_cached_entities(
&mut self,
world: &crate::ecs::world::World,
device: &wgpu::Device,
queue: &wgpu::Queue,
) {
let dirty_transforms = self
.frame_dirty
.as_mut()
.map(|fd| std::mem::take(&mut fd.transform_dirty))
.unwrap_or_default();
let world_state = self.world_states.get_mut(&self.current_world_id).unwrap();
let gpu = world_state.gpu_buffers.as_ref().unwrap();
for entity in &dirty_transforms {
if let Some(&slot) = world_state.gpu_registry.entity_to_slot.get(entity)
&& let Some(transform) = world.core.get_global_transform(*entity)
{
let model_matrix = ModelMatrix {
model: transform.0.into(),
normal_matrix: compute_normal_matrix(&transform.0),
};
if (slot as usize) < world_state.cached_transforms.len() {
world_state.cached_transforms[slot as usize] = model_matrix;
}
let offset = (slot as u64) * std::mem::size_of::<ModelMatrix>() as u64;
queue.write_buffer(
&gpu.transform_buffer,
offset,
bytemuck::cast_slice(&[model_matrix]),
);
}
}
let regular_count = world_state.regular_object_count as usize;
let overlay_opaque_instances = std::mem::take(&mut world_state.overlay_opaque_instances);
let overlay_opaque_double_sided_instances =
std::mem::take(&mut world_state.overlay_opaque_double_sided_instances);
let overlay_transparent_instances =
std::mem::take(&mut world_state.overlay_transparent_instances);
world_state.opaque_instances.clear();
world_state.opaque_double_sided_instances.clear();
world_state.transparent_instances.clear();
let transparent_mat_ids = &world_state.cached_transparent_material_ids;
let double_sided_mat_ids = &world_state.cached_double_sided_material_ids;
for index in 0..regular_count.min(world_state.cached_objects.len()) {
if world_state
.gpu_registry
.slot_to_entity
.get(index)
.copied()
.flatten()
.is_none()
{
continue;
}
let obj = &world_state.cached_objects[index];
let is_transparent = transparent_mat_ids.contains(&obj.material_id);
let is_double_sided = double_sided_mat_ids.contains(&obj.material_id);
let object_index = index as u32;
let mesh_id = obj.mesh_id;
let material_id = obj.material_id;
let batch_list = if is_transparent {
&mut world_state.transparent_instances
} else if is_double_sided {
&mut world_state.opaque_double_sided_instances
} else {
&mut world_state.opaque_instances
};
if let Some(last) = batch_list.last_mut() {
if last.0 == mesh_id && last.1 == material_id && last.3 == object_index {
last.3 = object_index + 1;
} else {
batch_list.push((mesh_id, material_id, object_index, object_index + 1));
}
} else {
batch_list.push((mesh_id, material_id, object_index, object_index + 1));
}
}
if !self.mesh_lod_mesh_ids.is_empty() {
for batch_list in [
&mut world_state.opaque_instances,
&mut world_state.opaque_double_sided_instances,
&mut world_state.transparent_instances,
] {
let original = std::mem::take(batch_list);
for (mesh_id, material_id, start, end) in original {
if let Some(lod_ids) = self.mesh_lod_mesh_ids.get(&mesh_id) {
for &lod_mesh_id in lod_ids {
batch_list.push((lod_mesh_id, material_id, start, end));
}
} else {
batch_list.push((mesh_id, material_id, start, end));
}
}
}
}
world_state.overlay_opaque_instances = overlay_opaque_instances;
world_state.overlay_opaque_double_sided_instances = overlay_opaque_double_sided_instances;
world_state.overlay_transparent_instances = overlay_transparent_instances;
let lod_sub_mesh_ids: std::collections::HashSet<u32> = self
.mesh_lod_mesh_ids
.values()
.flat_map(|ids| ids.iter().skip(1).copied())
.collect();
let all_regular_batches: Vec<BatchRange> = world_state
.opaque_instances
.iter()
.chain(world_state.opaque_double_sided_instances.iter())
.chain(world_state.transparent_instances.iter())
.cloned()
.collect();
for (batch_index, &(mesh_id, _, start, end)) in all_regular_batches.iter().enumerate() {
if lod_sub_mesh_ids.contains(&mesh_id) {
continue;
}
let batch_id = batch_index as u32;
for slot in start..end {
if (slot as usize) < world_state.cached_objects.len() {
world_state.cached_objects[slot as usize].batch_id = batch_id;
}
}
}
let overlay_batch_start = all_regular_batches.len() as u32;
let all_overlay_batches: Vec<BatchRange> = world_state
.overlay_opaque_instances
.iter()
.chain(world_state.overlay_opaque_double_sided_instances.iter())
.chain(world_state.overlay_transparent_instances.iter())
.cloned()
.collect();
for (batch_index, &(mesh_id, _, start, end)) in all_overlay_batches.iter().enumerate() {
if lod_sub_mesh_ids.contains(&mesh_id) {
continue;
}
let batch_id = overlay_batch_start + batch_index as u32;
for slot in start..end {
if (slot as usize) < world_state.cached_objects.len() {
world_state.cached_objects[slot as usize].batch_id = batch_id;
}
}
}
let instanced_batch_start = overlay_batch_start + all_overlay_batches.len() as u32;
let all_instanced_batches: Vec<BatchRange> = world_state
.instanced_opaque_batches
.iter()
.chain(world_state.instanced_opaque_double_sided_batches.iter())
.chain(world_state.instanced_transparent_batches.iter())
.cloned()
.collect();
for (batch_index, &(mesh_id, _, start, end)) in all_instanced_batches.iter().enumerate() {
if lod_sub_mesh_ids.contains(&mesh_id) {
continue;
}
let batch_id = instanced_batch_start + batch_index as u32;
for slot in start..end {
if (slot as usize) < world_state.cached_objects.len() {
world_state.cached_objects[slot as usize].batch_id = batch_id;
}
}
}
{
let gpu = world_state.gpu_buffers.as_ref().unwrap();
queue.write_buffer(
&gpu.object_buffer,
0,
bytemuck::cast_slice(&world_state.cached_objects),
);
}
let all_instances: Vec<_> = world_state
.opaque_instances
.iter()
.chain(world_state.opaque_double_sided_instances.iter())
.chain(world_state.transparent_instances.iter())
.chain(world_state.overlay_opaque_instances.iter())
.chain(world_state.overlay_opaque_double_sided_instances.iter())
.chain(world_state.overlay_transparent_instances.iter())
.chain(world_state.instanced_opaque_batches.iter())
.chain(world_state.instanced_opaque_double_sided_batches.iter())
.chain(world_state.instanced_transparent_batches.iter())
.cloned()
.collect();
let total_batch_count = all_instances.len();
let total_visible_slots: usize = all_instances
.iter()
.map(|&(_, _, start, end)| (end - start) as usize)
.sum();
if total_batch_count
> world_state
.gpu_buffers
.as_ref()
.unwrap()
.indirect_buffer_size
{
let new_size = std::cmp::min(
(total_batch_count as f32 * BUFFER_GROWTH_FACTOR).ceil() as usize,
MAX_INSTANCES,
);
let gpu_mut = world_state.gpu_buffers.as_mut().unwrap();
gpu_mut.indirect_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Indirect Draw Buffer (Resized)"),
size: (std::mem::size_of::<DrawIndexedIndirect>() * new_size) as u64,
usage: wgpu::BufferUsages::INDIRECT
| wgpu::BufferUsages::STORAGE
| wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
gpu_mut.indirect_buffer_size = new_size;
gpu_mut.phase1_indirect_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Phase 1 Indirect Buffer (Resized)"),
size: (std::mem::size_of::<DrawIndexedIndirect>() * new_size) as u64,
usage: wgpu::BufferUsages::INDIRECT
| wgpu::BufferUsages::STORAGE
| wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
gpu_mut.indirect_reset_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Indirect Reset Buffer (Resized)"),
size: (std::mem::size_of::<DrawIndexedIndirect>() * new_size) as u64,
usage: wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
gpu_mut.phase1_indirect_reset_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Phase 1 Indirect Reset Buffer (Resized)"),
size: (std::mem::size_of::<DrawIndexedIndirect>() * new_size) as u64,
usage: wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
gpu_mut.culling_bind_group = None;
gpu_mut.phase1_culling_bind_group = None;
}
let gpu = world_state.gpu_buffers.as_ref().unwrap();
let mut indirect_commands = Vec::with_capacity(total_batch_count);
let mut indirect_reset_commands = Vec::with_capacity(total_batch_count);
let mut visible_indices = Vec::with_capacity(total_visible_slots);
for &(mesh_id, _material_id, start, end) in all_instances.iter() {
let mesh_data = &self.mesh_data[mesh_id as usize];
let instance_count = end - start;
let first_instance = visible_indices.len() as u32;
let is_lod_sub = lod_sub_mesh_ids.contains(&mesh_id);
indirect_commands.push(DrawIndexedIndirect {
index_count: mesh_data.index_count,
instance_count: if is_lod_sub { 0 } else { instance_count },
first_index: mesh_data.index_offset,
base_vertex: mesh_data.vertex_offset as i32,
first_instance,
});
indirect_reset_commands.push(DrawIndexedIndirect {
index_count: mesh_data.index_count,
instance_count: 0,
first_index: mesh_data.index_offset,
base_vertex: mesh_data.vertex_offset as i32,
first_instance,
});
for object_index in start..end {
visible_indices.push(object_index);
}
}
queue.write_buffer(
&gpu.indirect_buffer,
0,
bytemuck::cast_slice(&indirect_commands),
);
queue.write_buffer(
&gpu.visible_indices_buffer,
0,
bytemuck::cast_slice(&visible_indices),
);
queue.write_buffer(
&gpu.phase1_visible_indices_buffer,
0,
bytemuck::cast_slice(&visible_indices),
);
queue.write_buffer(
&gpu.indirect_reset_buffer,
0,
bytemuck::cast_slice(&indirect_reset_commands),
);
queue.write_buffer(
&gpu.phase1_indirect_reset_buffer,
0,
bytemuck::cast_slice(&indirect_reset_commands),
);
world_state.indirect_reset_count = total_batch_count;
}
pub(in super::super) fn push_batch_with_lod(
&self,
instances: &mut Vec<(u32, u32, u32, u32)>,
mesh_id: u32,
material_id: u32,
start: u32,
end: u32,
) {
if let Some(lod_ids) = self.mesh_lod_mesh_ids.get(&mesh_id) {
for &lod_mesh_id in lod_ids {
instances.push((lod_mesh_id, material_id, start, end));
}
} else {
instances.push((mesh_id, material_id, start, end));
}
}
pub(in super::super) fn draw_batches<'a>(
pass: &mut wgpu::RenderPass<'a>,
instances: &[(u32, u32, u32, u32)],
batch_offset: usize,
material_bind_groups: &'a std::collections::HashMap<u32, wgpu::BindGroup>,
indirect_buffer: &'a wgpu::Buffer,
) {
let mut current_material: Option<u32> = None;
let mut batch_start_index: usize = 0;
for (batch_index, &(_mesh_id, material_id, _start, _end)) in instances.iter().enumerate() {
if current_material != Some(material_id) {
if let Some(prev_material) = current_material {
let count = batch_index - batch_start_index;
if count > 0
&& let Some(bind_group) = material_bind_groups.get(&prev_material)
{
pass.set_bind_group(2, bind_group, &[]);
let indirect_offset = ((batch_offset + batch_start_index)
* std::mem::size_of::<DrawIndexedIndirect>())
as u64;
pass.multi_draw_indexed_indirect(
indirect_buffer,
indirect_offset,
count as u32,
);
}
}
current_material = Some(material_id);
batch_start_index = batch_index;
}
}
if let Some(material_id) = current_material {
let count = instances.len() - batch_start_index;
if count > 0
&& let Some(bind_group) = material_bind_groups.get(&material_id)
{
pass.set_bind_group(2, bind_group, &[]);
let indirect_offset = ((batch_offset + batch_start_index)
* std::mem::size_of::<DrawIndexedIndirect>())
as u64;
pass.multi_draw_indexed_indirect(indirect_buffer, indirect_offset, count as u32);
}
}
}
pub(in super::super) fn compute_vertex_utilization(&self) -> f32 {
let actual_vertex_count: u64 = self.mesh_data.iter().map(|m| m.vertex_count as u64).sum();
let actual_vertex_bytes = actual_vertex_count * std::mem::size_of::<Vertex>() as u64;
if self.vertex_buffer_size > 0 {
actual_vertex_bytes as f32 / self.vertex_buffer_size as f32
} else {
1.0
}
}
pub(in super::super) fn compute_index_utilization(&self) -> f32 {
let actual_index_count: u64 = self.mesh_data.iter().map(|m| m.index_count as u64).sum();
let actual_index_bytes = actual_index_count * std::mem::size_of::<u32>() as u64;
if self.index_buffer_size > 0 {
actual_index_bytes as f32 / self.index_buffer_size as f32
} else {
1.0
}
}
pub(in super::super) fn check_and_compact_buffers(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
mesh_cache: &crate::ecs::prefab::resources::MeshCache,
) {
let actual_vertex_count: u64 = self.mesh_data.iter().map(|m| m.vertex_count as u64).sum();
let actual_index_count: u64 = self.mesh_data.iter().map(|m| m.index_count as u64).sum();
let actual_vertex_bytes = actual_vertex_count * std::mem::size_of::<Vertex>() as u64;
let actual_index_bytes = actual_index_count * std::mem::size_of::<u32>() as u64;
let vertex_utilization = if self.vertex_buffer_size > 0 {
actual_vertex_bytes as f32 / self.vertex_buffer_size as f32
} else {
1.0
};
let index_utilization = if self.index_buffer_size > 0 {
actual_index_bytes as f32 / self.index_buffer_size as f32
} else {
1.0
};
let should_compact = vertex_utilization < BUFFER_SHRINK_THRESHOLD
|| index_utilization < BUFFER_SHRINK_THRESHOLD;
if should_compact && actual_vertex_bytes > 0 && actual_index_bytes > 0 {
self.meshes.clear();
self.mesh_data.clear();
self.current_vertex_offset = 0;
self.current_index_offset = 0;
self.current_morph_displacement_offset = 0;
self.add_mesh(device, queue, "Cube", create_cube_mesh());
self.add_mesh(device, queue, "Sphere", create_sphere_mesh(1.0, 16));
self.add_mesh(device, queue, "Sphere_LOD1", create_sphere_mesh(1.0, 8));
self.add_mesh(device, queue, "Sphere_LOD2", create_sphere_mesh(1.0, 4));
self.add_mesh(device, queue, "Plane", create_plane_mesh(2.0));
self.add_mesh(
device,
queue,
"SubdividedPlane",
create_subdivided_plane_mesh(2.0, 20),
);
self.add_mesh(
device,
queue,
"Cylinder",
create_cylinder_mesh(0.5, 1.0, 16),
);
self.add_mesh(device, queue, "Cone", create_cone_mesh(0.5, 1.0, 16));
self.add_mesh(device, queue, "Torus", create_torus_mesh(1.0, 0.3, 16, 16));
for (name, mesh) in mesh_cache_iter(mesh_cache) {
if !self.meshes.contains_key(name) {
self.add_mesh(device, queue, name, mesh.clone());
}
}
let new_vertex_size = (actual_vertex_bytes as f32 * BUFFER_GROWTH_FACTOR).ceil() as u64;
let new_index_size = (actual_index_bytes as f32 * BUFFER_GROWTH_FACTOR).ceil() as u64;
if new_vertex_size < self.vertex_buffer_size {
let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Mesh Vertex Buffer (Compacted)"),
size: new_vertex_size,
usage: wgpu::BufferUsages::VERTEX
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Mesh Vertex Buffer Compaction"),
});
encoder.copy_buffer_to_buffer(
&self.vertex_buffer,
0,
&new_buffer,
0,
actual_vertex_bytes,
);
queue.submit(std::iter::once(encoder.finish()));
self.vertex_buffer = new_buffer;
self.vertex_buffer_size = new_vertex_size;
}
if new_index_size < self.index_buffer_size {
let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Mesh Index Buffer (Compacted)"),
size: new_index_size,
usage: wgpu::BufferUsages::INDEX
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Mesh Index Buffer Compaction"),
});
encoder.copy_buffer_to_buffer(
&self.index_buffer,
0,
&new_buffer,
0,
actual_index_bytes,
);
queue.submit(std::iter::once(encoder.finish()));
self.index_buffer = new_buffer;
self.index_buffer_size = new_index_size;
}
}
}
}