From 063a8b1b1afb1442de187786b4c6bb7f27175265 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Tue, 8 Oct 2024 23:15:36 -0700 Subject: [PATCH 01/29] WIP: Proper error projection --- crates/bevy_pbr/src/meshlet/asset.rs | 28 +++- crates/bevy_pbr/src/meshlet/from_mesh.rs | 140 ++++++++++++------ .../src/meshlet/meshlet_bindings.wgsl | 32 ++-- .../src/meshlet/meshlet_mesh_manager.rs | 22 ++- .../src/meshlet/persistent_buffer_impls.rs | 68 +++++---- .../bevy_pbr/src/meshlet/resource_manager.rs | 3 + 6 files changed, 195 insertions(+), 98 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/asset.rs b/crates/bevy_pbr/src/meshlet/asset.rs index 4e3edb32a959d..9a68c1acd646e 100644 --- a/crates/bevy_pbr/src/meshlet/asset.rs +++ b/crates/bevy_pbr/src/meshlet/asset.rs @@ -51,6 +51,8 @@ pub struct MeshletMesh { pub(crate) meshlets: Arc<[Meshlet]>, /// Spherical bounding volumes. pub(crate) meshlet_bounding_spheres: Arc<[MeshletBoundingSpheres]>, + /// Meshlet simplification errors. + pub(crate) meshlet_simplification_errors: Arc<[MeshletSimplificationError]>, } /// A single meshlet within a [`MeshletMesh`]. @@ -90,12 +92,12 @@ pub struct Meshlet { #[derive(Copy, Clone, Pod, Zeroable)] #[repr(C)] pub struct MeshletBoundingSpheres { - /// The bounding sphere used for frustum and occlusion culling for this meshlet. - pub self_culling: MeshletBoundingSphere, - /// The bounding sphere used for determining if this meshlet is at the correct level of detail for a given view. - pub self_lod: MeshletBoundingSphere, - /// The bounding sphere used for determining if this meshlet's parent is at the correct level of detail for a given view. - pub parent_lod: MeshletBoundingSphere, + /// Bounding sphere used for frustum and occlusion culling for this meshlet. + pub culling_sphere: MeshletBoundingSphere, + /// Bounding sphere used for determining if this meshlet's group is at the correct level of detail for a given view. + pub group_lod_sphere: MeshletBoundingSphere, + /// Bounding sphere used for determining if this meshlet's parent group is at the correct level of detail for a given view. + pub parent_group_lod_sphere: MeshletBoundingSphere, } /// A spherical bounding volume used for a [`Meshlet`]. @@ -106,6 +108,17 @@ pub struct MeshletBoundingSphere { pub radius: f32, } +/// Simplification error used for choosing level of detail for a [`Meshlet`]. +// TODO: pack2x16float +#[derive(Copy, Clone, Pod, Zeroable)] +#[repr(C)] +pub struct MeshletSimplificationError { + /// Simplification error used for determining if this meshlet's group is at the correct level of detail for a given view. + pub group_error: f32, + /// Simplification error used for determining if this meshlet's parent group is at the correct level of detail for a given view. + pub parent_group_error: f32, +} + /// An [`AssetSaver`] for `.meshlet_mesh` [`MeshletMesh`] assets. pub struct MeshletMeshSaver; @@ -139,6 +152,7 @@ impl AssetSaver for MeshletMeshSaver { write_slice(&asset.indices, &mut writer)?; write_slice(&asset.meshlets, &mut writer)?; write_slice(&asset.meshlet_bounding_spheres, &mut writer)?; + write_slice(&asset.meshlet_simplification_errors, &mut writer)?; writer.finish()?; Ok(()) @@ -179,6 +193,7 @@ impl AssetLoader for MeshletMeshLoader { let indices = read_slice(reader)?; let meshlets = read_slice(reader)?; let meshlet_bounding_spheres = read_slice(reader)?; + let meshlet_simplification_errors = read_slice(reader)?; Ok(MeshletMesh { vertex_positions, @@ -187,6 +202,7 @@ impl AssetLoader for MeshletMeshLoader { indices, meshlets, meshlet_bounding_spheres, + meshlet_simplification_errors, }) } diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index b8cdd1fc1144d..65f2eea6fd4b7 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -1,4 +1,6 @@ -use super::asset::{Meshlet, MeshletBoundingSphere, MeshletBoundingSpheres, MeshletMesh}; +use super::asset::{ + Meshlet, MeshletBoundingSphere, MeshletBoundingSpheres, MeshletMesh, MeshletSimplificationError, +}; use alloc::borrow::Cow; use bevy_math::{ops::log2, IVec3, Vec2, Vec3, Vec3Swizzles}; use bevy_render::{ @@ -11,12 +13,11 @@ use core::ops::Range; use derive_more::derive::{Display, Error}; use itertools::Itertools; use meshopt::{ - build_meshlets, compute_cluster_bounds, compute_meshlet_bounds, - ffi::{meshopt_Bounds, meshopt_Meshlet}, - simplify, Meshlets, SimplifyOptions, VertexDataAdapter, + build_meshlets, ffi::meshopt_Meshlet, simplify, Meshlets, SimplifyOptions, VertexDataAdapter, }; use metis::Graph; use smallvec::SmallVec; +use std::iter; /// Default vertex position quantization factor for use with [`MeshletMesh::from_mesh`]. /// @@ -69,19 +70,21 @@ impl MeshletMesh { let mut bounding_spheres = meshlets .iter() .map(|meshlet| compute_meshlet_bounds(meshlet, &vertices)) - .map(convert_meshlet_bounds) .map(|bounding_sphere| MeshletBoundingSpheres { - self_culling: bounding_sphere, - self_lod: MeshletBoundingSphere { - center: bounding_sphere.center, + culling_sphere: bounding_sphere, + group_lod_sphere: bounding_sphere, + parent_group_lod_sphere: MeshletBoundingSphere { + center: Vec3::ZERO, radius: 0.0, }, - parent_lod: MeshletBoundingSphere { - center: bounding_sphere.center, - radius: f32::MAX, - }, }) .collect::>(); + let mut simplification_errors = iter::repeat(MeshletSimplificationError { + group_error: 0.0, + parent_group_error: f32::MAX, + }) + .take(meshlets.len()) + .collect::>(); // Build further LODs let mut simplification_queue = 0..meshlets.len(); @@ -107,22 +110,13 @@ impl MeshletMesh { continue; }; - // Force parent error to be >= child error (we're currently building the parent from its children) - group_error = group_meshlets.iter().fold(group_error, |acc, meshlet_id| { - acc.max(bounding_spheres[*meshlet_id].self_lod.radius) - }); - - // Build a new LOD bounding sphere for the simplified group as a whole - let mut group_bounding_sphere = convert_meshlet_bounds(compute_cluster_bounds( - &simplified_group_indices, - &vertices, - )); - group_bounding_sphere.radius = group_error; - - // For each meshlet in the group set their parent LOD bounding sphere to that of the simplified group - for meshlet_id in group_meshlets { - bounding_spheres[meshlet_id].parent_lod = group_bounding_sphere; - } + // Compute LOD data for the group + let group_bounding_sphere = compute_group_lod_data( + &group_meshlets, + &mut group_error, + &mut bounding_spheres, + &simplification_errors, + ); // Build new meshlets using the simplified group let new_meshlets_count = split_simplified_group_into_new_meshlets( @@ -131,22 +125,24 @@ impl MeshletMesh { &mut meshlets, ); - // Calculate the culling bounding sphere for the new meshlets and set their LOD bounding spheres + // Calculate the culling bounding sphere for the new meshlets and set their group LOD data let new_meshlet_ids = (meshlets.len() - new_meshlets_count)..meshlets.len(); - bounding_spheres.extend( - new_meshlet_ids - .map(|meshlet_id| { - compute_meshlet_bounds(meshlets.get(meshlet_id), &vertices) - }) - .map(convert_meshlet_bounds) - .map(|bounding_sphere| MeshletBoundingSpheres { - self_culling: bounding_sphere, - self_lod: group_bounding_sphere, - parent_lod: MeshletBoundingSphere { - center: group_bounding_sphere.center, - radius: f32::MAX, - }, - }), + bounding_spheres.extend(new_meshlet_ids.clone().map(|meshlet_id| { + MeshletBoundingSpheres { + culling_sphere: compute_meshlet_bounds(meshlets.get(meshlet_id), &vertices), + group_lod_sphere: group_bounding_sphere, + parent_group_lod_sphere: MeshletBoundingSphere { + center: Vec3::ZERO, + radius: 0.0, + }, + } + })); + simplification_errors.extend( + iter::repeat(MeshletSimplificationError { + group_error, + parent_group_error: f32::MAX, + }) + .take(new_meshlet_ids.len()), ); } @@ -179,6 +175,7 @@ impl MeshletMesh { indices: meshlets.triangles.into(), meshlets: bevy_meshlets.into(), meshlet_bounding_spheres: bounding_spheres.into(), + meshlet_simplification_errors: simplification_errors.into(), }) } } @@ -330,12 +327,57 @@ fn simplify_meshlet_group( return None; } - // Convert error from diameter to radius - error *= 0.5; - Some((simplified_group_indices, error)) } +fn compute_group_lod_data( + group_meshlets: &[usize], + group_error: &mut f32, + bounding_spheres: &mut [MeshletBoundingSpheres], + simplification_errors: &[MeshletSimplificationError], +) -> MeshletBoundingSphere { + let mut group_bounding_sphere = MeshletBoundingSphere { + center: Vec3::ZERO, + radius: 0.0, + }; + + // Compute the group lod sphere center as a weighted average of the children spheres + let mut weight = 0.0; + for meshlet_id in group_meshlets { + let meshlet_lod_bounding_sphere = bounding_spheres[*meshlet_id].group_lod_sphere; + group_bounding_sphere.center += + meshlet_lod_bounding_sphere.center * meshlet_lod_bounding_sphere.radius; + weight += meshlet_lod_bounding_sphere.radius; + } + // TODO: Check needed? + // if weight > 0.0 { + group_bounding_sphere.center /= weight; + // } + + // Force parent group sphere to contain all child group spheres (we're currently building the parent from its children) + // TODO: This does not produce the absolute minimal bounding sphere. Doing so is non-trivial. + // "Smallest enclosing balls of balls" http://www.inf.ethz.ch/personal/emo/DoctThesisFiles/fischer05.pdf + for meshlet_id in group_meshlets { + let meshlet_lod_bounding_sphere = bounding_spheres[*meshlet_id].group_lod_sphere; + let d = meshlet_lod_bounding_sphere + .center + .distance(group_bounding_sphere.center); + group_bounding_sphere.radius = group_bounding_sphere + .radius + .max(meshlet_lod_bounding_sphere.radius + d); + } + + for meshlet_id in group_meshlets { + // Force parent error to be >= child error (we're currently building the parent from its children) + *group_error = group_error.max(simplification_errors[*meshlet_id].group_error); + + // Set the children's parent group lod sphere to the new sphere we made for this group + bounding_spheres[*meshlet_id].parent_group_lod_sphere = group_bounding_sphere; + } + + group_bounding_sphere +} + fn split_simplified_group_into_new_meshlets( simplified_group_indices: &[u32], vertices: &VertexDataAdapter<'_>, @@ -449,7 +491,11 @@ fn build_and_compress_meshlet_vertex_data( }); } -fn convert_meshlet_bounds(bounds: meshopt_Bounds) -> MeshletBoundingSphere { +fn compute_meshlet_bounds( + meshlet: meshopt::Meshlet<'_>, + vertices: &VertexDataAdapter<'_>, +) -> MeshletBoundingSphere { + let bounds = meshopt::compute_meshlet_bounds(meshlet, vertices); MeshletBoundingSphere { center: bounds.center.into(), radius: bounds.radius, diff --git a/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl b/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl index f870758353fdf..04bab2688b3d9 100644 --- a/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl +++ b/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl @@ -25,9 +25,9 @@ fn get_meshlet_triangle_count(meshlet: ptr) -> u32 { } struct MeshletBoundingSpheres { - self_culling: MeshletBoundingSphere, - self_lod: MeshletBoundingSphere, - parent_lod: MeshletBoundingSphere, + culling_sphere: MeshletBoundingSphere, + group_lod_sphere: MeshletBoundingSphere, + parent_group_lod_sphere: MeshletBoundingSphere, } struct MeshletBoundingSphere { @@ -35,6 +35,11 @@ struct MeshletBoundingSphere { radius: f32, } +struct MeshletSimplificationError { + group_error: f32, + parent_group_error: f32, +} + struct DispatchIndirectArgs { x: atomic, y: u32, @@ -62,16 +67,17 @@ var cluster_count: u32; var meshlet_raster_cluster_rightmost_slot: u32; @group(0) @binding(0) var meshlet_cluster_meshlet_ids: array; // Per cluster @group(0) @binding(1) var meshlet_bounding_spheres: array; // Per meshlet -@group(0) @binding(2) var meshlet_cluster_instance_ids: array; // Per cluster -@group(0) @binding(3) var meshlet_instance_uniforms: array; // Per entity instance -@group(0) @binding(4) var meshlet_view_instance_visibility: array; // 1 bit per entity instance, packed as a bitmask -@group(0) @binding(5) var meshlet_second_pass_candidates: array>; // 1 bit per cluster , packed as a bitmask -@group(0) @binding(6) var meshlet_software_raster_indirect_args: DispatchIndirectArgs; // Single object shared between all workgroups/clusters/triangles -@group(0) @binding(7) var meshlet_hardware_raster_indirect_args: DrawIndirectArgs; // Single object shared between all workgroups/clusters/triangles -@group(0) @binding(8) var meshlet_raster_clusters: array; // Single object shared between all workgroups/clusters/triangles -@group(0) @binding(9) var depth_pyramid: texture_2d; // From the end of the last frame for the first culling pass, and from the first raster pass for the second culling pass -@group(0) @binding(10) var view: View; -@group(0) @binding(11) var previous_view: PreviousViewUniforms; +@group(0) @binding(2) var meshlet_simplification_errors: array; // Per meshlet +@group(0) @binding(3) var meshlet_cluster_instance_ids: array; // Per cluster +@group(0) @binding(4) var meshlet_instance_uniforms: array; // Per entity instance +@group(0) @binding(5) var meshlet_view_instance_visibility: array; // 1 bit per entity instance, packed as a bitmask +@group(0) @binding(6) var meshlet_second_pass_candidates: array>; // 1 bit per cluster , packed as a bitmask +@group(0) @binding(7) var meshlet_software_raster_indirect_args: DispatchIndirectArgs; // Single object shared between all workgroups/clusters/triangles +@group(0) @binding(8) var meshlet_hardware_raster_indirect_args: DrawIndirectArgs; // Single object shared between all workgroups/clusters/triangles +@group(0) @binding(9) var meshlet_raster_clusters: array; // Single object shared between all workgroups/clusters/triangles +@group(0) @binding(10) var depth_pyramid: texture_2d; // From the end of the last frame for the first culling pass, and from the first raster pass for the second culling pass +@group(0) @binding(11) var view: View; +@group(0) @binding(12) var previous_view: PreviousViewUniforms; fn should_cull_instance(instance_id: u32) -> bool { let bit_offset = instance_id % 32u; diff --git a/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs b/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs index 831fe11fb353b..4170ac6279446 100644 --- a/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs +++ b/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs @@ -1,5 +1,5 @@ use super::{ - asset::{Meshlet, MeshletBoundingSpheres}, + asset::{Meshlet, MeshletBoundingSpheres, MeshletSimplificationError}, persistent_buffer::PersistentGpuBuffer, MeshletMesh, }; @@ -26,7 +26,8 @@ pub struct MeshletMeshManager { pub indices: PersistentGpuBuffer>, pub meshlets: PersistentGpuBuffer>, pub meshlet_bounding_spheres: PersistentGpuBuffer>, - meshlet_mesh_slices: HashMap, [Range; 6]>, + pub meshlet_simplification_errors: PersistentGpuBuffer>, + meshlet_mesh_slices: HashMap, [Range; 7]>, } impl FromWorld for MeshletMeshManager { @@ -42,6 +43,10 @@ impl FromWorld for MeshletMeshManager { "meshlet_bounding_spheres", render_device, ), + meshlet_simplification_errors: PersistentGpuBuffer::new( + "meshlet_simplification_errors", + render_device, + ), meshlet_mesh_slices: HashMap::new(), } } @@ -81,6 +86,9 @@ impl MeshletMeshManager { let meshlet_bounding_spheres_slice = self .meshlet_bounding_spheres .queue_write(Arc::clone(&meshlet_mesh.meshlet_bounding_spheres), ()); + let meshlet_simplification_errors_slice = self + .meshlet_simplification_errors + .queue_write(Arc::clone(&meshlet_mesh.meshlet_simplification_errors), ()); [ vertex_positions_slice, @@ -89,11 +97,12 @@ impl MeshletMeshManager { indices_slice, meshlets_slice, meshlet_bounding_spheres_slice, + meshlet_simplification_errors_slice, ] }; // If the MeshletMesh asset has not been uploaded to the GPU yet, queue it for uploading - let [_, _, _, _, meshlets_slice, _] = self + let [_, _, _, _, meshlets_slice, _, _] = self .meshlet_mesh_slices .entry(asset_id) .or_insert_with_key(queue_meshlet_mesh) @@ -106,7 +115,7 @@ impl MeshletMeshManager { pub fn remove(&mut self, asset_id: &AssetId) { if let Some( - [vertex_positions_slice, vertex_normals_slice, vertex_uvs_slice, indices_slice, meshlets_slice, meshlet_bounding_spheres_slice], + [vertex_positions_slice, vertex_normals_slice, vertex_uvs_slice, indices_slice, meshlets_slice, meshlet_bounding_spheres_slice, meshlet_simplification_errors_slice], ) = self.meshlet_mesh_slices.remove(asset_id) { self.vertex_positions @@ -117,6 +126,8 @@ impl MeshletMeshManager { self.meshlets.mark_slice_unused(meshlets_slice); self.meshlet_bounding_spheres .mark_slice_unused(meshlet_bounding_spheres_slice); + self.meshlet_simplification_errors + .mark_slice_unused(meshlet_simplification_errors_slice); } } } @@ -145,4 +156,7 @@ pub fn perform_pending_meshlet_mesh_writes( meshlet_mesh_manager .meshlet_bounding_spheres .perform_writes(&render_queue, &render_device); + meshlet_mesh_manager + .meshlet_simplification_errors + .perform_writes(&render_queue, &render_device); } diff --git a/crates/bevy_pbr/src/meshlet/persistent_buffer_impls.rs b/crates/bevy_pbr/src/meshlet/persistent_buffer_impls.rs index 9fa7058772a90..9c2667d3f3d4d 100644 --- a/crates/bevy_pbr/src/meshlet/persistent_buffer_impls.rs +++ b/crates/bevy_pbr/src/meshlet/persistent_buffer_impls.rs @@ -1,10 +1,42 @@ use super::{ - asset::{Meshlet, MeshletBoundingSpheres}, + asset::{Meshlet, MeshletBoundingSpheres, MeshletSimplificationError}, persistent_buffer::PersistentGpuBufferable, }; use alloc::sync::Arc; use bevy_math::Vec2; +impl PersistentGpuBufferable for Arc<[Meshlet]> { + type Metadata = (u64, u64, u64); + + fn size_in_bytes(&self) -> usize { + self.len() * size_of::() + } + + fn write_bytes_le( + &self, + (vertex_position_offset, vertex_attribute_offset, index_offset): Self::Metadata, + buffer_slice: &mut [u8], + ) { + let vertex_position_offset = (vertex_position_offset * 8) as u32; + let vertex_attribute_offset = (vertex_attribute_offset as usize / size_of::()) as u32; + let index_offset = index_offset as u32; + + for (i, meshlet) in self.iter().enumerate() { + let size = size_of::(); + let i = i * size; + let bytes = bytemuck::cast::<_, [u8; size_of::()]>(Meshlet { + start_vertex_position_bit: meshlet.start_vertex_position_bit + + vertex_position_offset, + start_vertex_attribute_id: meshlet.start_vertex_attribute_id + + vertex_attribute_offset, + start_index_id: meshlet.start_index_id + index_offset, + ..*meshlet + }); + buffer_slice[i..(i + size)].clone_from_slice(&bytes); + } + } +} + impl PersistentGpuBufferable for Arc<[u8]> { type Metadata = (); @@ -41,43 +73,23 @@ impl PersistentGpuBufferable for Arc<[Vec2]> { } } -impl PersistentGpuBufferable for Arc<[Meshlet]> { - type Metadata = (u64, u64, u64); +impl PersistentGpuBufferable for Arc<[MeshletBoundingSpheres]> { + type Metadata = (); fn size_in_bytes(&self) -> usize { - self.len() * size_of::() + self.len() * size_of::() } - fn write_bytes_le( - &self, - (vertex_position_offset, vertex_attribute_offset, index_offset): Self::Metadata, - buffer_slice: &mut [u8], - ) { - let vertex_position_offset = (vertex_position_offset * 8) as u32; - let vertex_attribute_offset = (vertex_attribute_offset as usize / size_of::()) as u32; - let index_offset = index_offset as u32; - - for (i, meshlet) in self.iter().enumerate() { - let size = size_of::(); - let i = i * size; - let bytes = bytemuck::cast::<_, [u8; size_of::()]>(Meshlet { - start_vertex_position_bit: meshlet.start_vertex_position_bit - + vertex_position_offset, - start_vertex_attribute_id: meshlet.start_vertex_attribute_id - + vertex_attribute_offset, - start_index_id: meshlet.start_index_id + index_offset, - ..*meshlet - }); - buffer_slice[i..(i + size)].clone_from_slice(&bytes); - } + fn write_bytes_le(&self, _: Self::Metadata, buffer_slice: &mut [u8]) { + buffer_slice.clone_from_slice(bytemuck::cast_slice(self)); } } -impl PersistentGpuBufferable for Arc<[MeshletBoundingSpheres]> { +impl PersistentGpuBufferable for Arc<[MeshletSimplificationError]> { type Metadata = (); fn size_in_bytes(&self) -> usize { - self.len() * size_of::() + self.len() * size_of::() } fn write_bytes_le(&self, _: Self::Metadata, buffer_slice: &mut [u8]) { diff --git a/crates/bevy_pbr/src/meshlet/resource_manager.rs b/crates/bevy_pbr/src/meshlet/resource_manager.rs index fd95f45b3f26e..edfc7ba5f3da6 100644 --- a/crates/bevy_pbr/src/meshlet/resource_manager.rs +++ b/crates/bevy_pbr/src/meshlet/resource_manager.rs @@ -135,6 +135,7 @@ impl ResourceManager { storage_buffer_read_only_sized(false, None), storage_buffer_read_only_sized(false, None), storage_buffer_read_only_sized(false, None), + storage_buffer_read_only_sized(false, None), storage_buffer_sized(false, None), storage_buffer_sized(false, None), storage_buffer_sized(false, None), @@ -624,6 +625,7 @@ pub fn prepare_meshlet_view_bind_groups( let entries = BindGroupEntries::sequential(( cluster_meshlet_ids.as_entire_binding(), meshlet_mesh_manager.meshlet_bounding_spheres.binding(), + meshlet_mesh_manager.meshlet_simplification_errors.binding(), cluster_instance_ids.as_entire_binding(), instance_manager.instance_uniforms.binding().unwrap(), view_resources.instance_visibility.as_entire_binding(), @@ -652,6 +654,7 @@ pub fn prepare_meshlet_view_bind_groups( let entries = BindGroupEntries::sequential(( cluster_meshlet_ids.as_entire_binding(), meshlet_mesh_manager.meshlet_bounding_spheres.binding(), + meshlet_mesh_manager.meshlet_simplification_errors.binding(), cluster_instance_ids.as_entire_binding(), instance_manager.instance_uniforms.binding().unwrap(), view_resources.instance_visibility.as_entire_binding(), From d8fd98033e1f60b90447a257accbc69ad0f46652 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Thu, 10 Oct 2024 00:23:19 -0700 Subject: [PATCH 02/29] WIP: LOD selection (not working), some cleanup --- crates/bevy_pbr/src/meshlet/asset.rs | 4 +- .../bevy_pbr/src/meshlet/cull_clusters.wgsl | 45 +++++++++---------- crates/bevy_pbr/src/meshlet/from_mesh.rs | 20 ++++----- .../src/meshlet/meshlet_bindings.wgsl | 4 +- 4 files changed, 34 insertions(+), 39 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/asset.rs b/crates/bevy_pbr/src/meshlet/asset.rs index 9a68c1acd646e..85226aeb4b8bb 100644 --- a/crates/bevy_pbr/src/meshlet/asset.rs +++ b/crates/bevy_pbr/src/meshlet/asset.rs @@ -95,9 +95,9 @@ pub struct MeshletBoundingSpheres { /// Bounding sphere used for frustum and occlusion culling for this meshlet. pub culling_sphere: MeshletBoundingSphere, /// Bounding sphere used for determining if this meshlet's group is at the correct level of detail for a given view. - pub group_lod_sphere: MeshletBoundingSphere, + pub lod_group_sphere: MeshletBoundingSphere, /// Bounding sphere used for determining if this meshlet's parent group is at the correct level of detail for a given view. - pub parent_group_lod_sphere: MeshletBoundingSphere, + pub lod_parent_group_sphere: MeshletBoundingSphere, } /// A spherical bounding volume used for a [`Meshlet`]. diff --git a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl index fe5df60f12082..320c0006333a8 100644 --- a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl +++ b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl @@ -1,6 +1,7 @@ #import bevy_pbr::meshlet_bindings::{ meshlet_cluster_meshlet_ids, meshlet_bounding_spheres, + meshlet_simplification_errors, meshlet_cluster_instance_ids, meshlet_instance_uniforms, meshlet_second_pass_candidates, @@ -48,8 +49,8 @@ fn cull_clusters( let world_from_local = affine3_to_square(instance_uniform.world_from_local); let world_scale = max(length(world_from_local[0]), max(length(world_from_local[1]), length(world_from_local[2]))); let bounding_spheres = meshlet_bounding_spheres[meshlet_id]; - let culling_bounding_sphere_center = world_from_local * vec4(bounding_spheres.self_culling.center, 1.0); - let culling_bounding_sphere_radius = world_scale * bounding_spheres.self_culling.radius; + let culling_bounding_sphere_center = world_from_local * vec4(bounding_spheres.culling_sphere.center, 1.0); + let culling_bounding_sphere_radius = world_scale * bounding_spheres.culling_sphere.radius; #ifdef MESHLET_FIRST_CULLING_PASS // Frustum culling @@ -60,19 +61,10 @@ fn cull_clusters( } } - // Calculate view-space LOD bounding sphere for the cluster - let lod_bounding_sphere_center = world_from_local * vec4(bounding_spheres.self_lod.center, 1.0); - let lod_bounding_sphere_radius = world_scale * bounding_spheres.self_lod.radius; - let lod_bounding_sphere_center_view_space = (view.view_from_world * vec4(lod_bounding_sphere_center.xyz, 1.0)).xyz; - - // Calculate view-space LOD bounding sphere for the cluster's parent - let parent_lod_bounding_sphere_center = world_from_local * vec4(bounding_spheres.parent_lod.center, 1.0); - let parent_lod_bounding_sphere_radius = world_scale * bounding_spheres.parent_lod.radius; - let parent_lod_bounding_sphere_center_view_space = (view.view_from_world * vec4(parent_lod_bounding_sphere_center.xyz, 1.0)).xyz; - - // Check LOD cut (cluster error imperceptible, and parent error not imperceptible) - let lod_is_ok = lod_error_is_imperceptible(lod_bounding_sphere_center_view_space, lod_bounding_sphere_radius); - let parent_lod_is_ok = lod_error_is_imperceptible(parent_lod_bounding_sphere_center_view_space, parent_lod_bounding_sphere_radius); + // Check LOD cut (cluster group error imperceptible, and parent group error not imperceptible) + let simplification_errors = meshlet_simplification_errors[meshlet_id]; + let lod_is_ok = lod_error_is_imperceptible(bounding_spheres.lod_group_sphere.center, bounding_spheres.lod_group_sphere.radius, simplification_errors.group_error, world_from_local, world_scale); + let parent_lod_is_ok = lod_error_is_imperceptible(bounding_spheres.lod_parent_group_sphere.center, bounding_spheres.lod_parent_group_sphere.radius, simplification_errors.parent_group_error, world_from_local, world_scale); if !lod_is_ok || parent_lod_is_ok { return; } #endif @@ -80,8 +72,8 @@ fn cull_clusters( #ifdef MESHLET_FIRST_CULLING_PASS let previous_world_from_local = affine3_to_square(instance_uniform.previous_world_from_local); let previous_world_from_local_scale = max(length(previous_world_from_local[0]), max(length(previous_world_from_local[1]), length(previous_world_from_local[2]))); - let occlusion_culling_bounding_sphere_center = previous_world_from_local * vec4(bounding_spheres.self_culling.center, 1.0); - let occlusion_culling_bounding_sphere_radius = previous_world_from_local_scale * bounding_spheres.self_culling.radius; + let occlusion_culling_bounding_sphere_center = previous_world_from_local * vec4(bounding_spheres.culling_sphere.center, 1.0); + let occlusion_culling_bounding_sphere_radius = previous_world_from_local_scale * bounding_spheres.culling_sphere.radius; let occlusion_culling_bounding_sphere_center_view_space = (previous_view.view_from_world * vec4(occlusion_culling_bounding_sphere_center.xyz, 1.0)).xyz; #else let occlusion_culling_bounding_sphere_center = culling_bounding_sphere_center; @@ -148,14 +140,17 @@ fn cull_clusters( meshlet_raster_clusters[buffer_slot] = cluster_id; } -// https://stackoverflow.com/questions/21648630/radius-of-projected-sphere-in-screen-space/21649403#21649403 -fn lod_error_is_imperceptible(cp: vec3, r: f32) -> bool { - let d2 = dot(cp, cp); - let r2 = r * r; - let sphere_diameter_uv = view.clip_from_view[0][0] * r / sqrt(d2 - r2); - let view_size = f32(max(view.viewport.z, view.viewport.w)); - let sphere_diameter_pixels = sphere_diameter_uv * view_size; - return sphere_diameter_pixels < 1.0; +fn lod_error_is_imperceptible(cp: vec3, r: f32, error: f32, world_from_local: mat4x4, world_scale: f32) -> bool { + let cp_world = world_from_local * vec4(cp, 1.0); + let r_view = world_scale * r; + let cp_view = (view.view_from_world * vec4(cp_world.xyz, 1.0)).xyz; + + // TODO: Handle view clipping / being inside sphere bounds + let aabb = project_view_space_sphere_to_screen_space_aabb(cp_view, r_view); + let screen_size = max(aabb.z - aabb.x, aabb.w - aabb.y); + let meters_per_pixel = r / screen_size; + + return error < meters_per_pixel; } // https://zeux.io/2023/01/12/approximate-projected-bounds diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index 65f2eea6fd4b7..9db2f3a72d17b 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -72,8 +72,8 @@ impl MeshletMesh { .map(|meshlet| compute_meshlet_bounds(meshlet, &vertices)) .map(|bounding_sphere| MeshletBoundingSpheres { culling_sphere: bounding_sphere, - group_lod_sphere: bounding_sphere, - parent_group_lod_sphere: MeshletBoundingSphere { + lod_group_sphere: bounding_sphere, + lod_parent_group_sphere: MeshletBoundingSphere { center: Vec3::ZERO, radius: 0.0, }, @@ -111,7 +111,7 @@ impl MeshletMesh { }; // Compute LOD data for the group - let group_bounding_sphere = compute_group_lod_data( + let group_bounding_sphere = compute_lod_group_data( &group_meshlets, &mut group_error, &mut bounding_spheres, @@ -125,13 +125,13 @@ impl MeshletMesh { &mut meshlets, ); - // Calculate the culling bounding sphere for the new meshlets and set their group LOD data + // Calculate the culling bounding sphere for the new meshlets and set their LOD group data let new_meshlet_ids = (meshlets.len() - new_meshlets_count)..meshlets.len(); bounding_spheres.extend(new_meshlet_ids.clone().map(|meshlet_id| { MeshletBoundingSpheres { culling_sphere: compute_meshlet_bounds(meshlets.get(meshlet_id), &vertices), - group_lod_sphere: group_bounding_sphere, - parent_group_lod_sphere: MeshletBoundingSphere { + lod_group_sphere: group_bounding_sphere, + lod_parent_group_sphere: MeshletBoundingSphere { center: Vec3::ZERO, radius: 0.0, }, @@ -330,7 +330,7 @@ fn simplify_meshlet_group( Some((simplified_group_indices, error)) } -fn compute_group_lod_data( +fn compute_lod_group_data( group_meshlets: &[usize], group_error: &mut f32, bounding_spheres: &mut [MeshletBoundingSpheres], @@ -344,7 +344,7 @@ fn compute_group_lod_data( // Compute the group lod sphere center as a weighted average of the children spheres let mut weight = 0.0; for meshlet_id in group_meshlets { - let meshlet_lod_bounding_sphere = bounding_spheres[*meshlet_id].group_lod_sphere; + let meshlet_lod_bounding_sphere = bounding_spheres[*meshlet_id].lod_group_sphere; group_bounding_sphere.center += meshlet_lod_bounding_sphere.center * meshlet_lod_bounding_sphere.radius; weight += meshlet_lod_bounding_sphere.radius; @@ -358,7 +358,7 @@ fn compute_group_lod_data( // TODO: This does not produce the absolute minimal bounding sphere. Doing so is non-trivial. // "Smallest enclosing balls of balls" http://www.inf.ethz.ch/personal/emo/DoctThesisFiles/fischer05.pdf for meshlet_id in group_meshlets { - let meshlet_lod_bounding_sphere = bounding_spheres[*meshlet_id].group_lod_sphere; + let meshlet_lod_bounding_sphere = bounding_spheres[*meshlet_id].lod_group_sphere; let d = meshlet_lod_bounding_sphere .center .distance(group_bounding_sphere.center); @@ -372,7 +372,7 @@ fn compute_group_lod_data( *group_error = group_error.max(simplification_errors[*meshlet_id].group_error); // Set the children's parent group lod sphere to the new sphere we made for this group - bounding_spheres[*meshlet_id].parent_group_lod_sphere = group_bounding_sphere; + bounding_spheres[*meshlet_id].lod_parent_group_sphere = group_bounding_sphere; } group_bounding_sphere diff --git a/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl b/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl index 04bab2688b3d9..5e5b9d139f576 100644 --- a/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl +++ b/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl @@ -26,8 +26,8 @@ fn get_meshlet_triangle_count(meshlet: ptr) -> u32 { struct MeshletBoundingSpheres { culling_sphere: MeshletBoundingSphere, - group_lod_sphere: MeshletBoundingSphere, - parent_group_lod_sphere: MeshletBoundingSphere, + lod_group_sphere: MeshletBoundingSphere, + lod_parent_group_sphere: MeshletBoundingSphere, } struct MeshletBoundingSphere { From 718e663057694d8d2de6da187716bda5ecb17946 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Thu, 10 Oct 2024 00:34:42 -0700 Subject: [PATCH 03/29] Fix: Forgot to set parent_group_error for children after building parent group --- crates/bevy_pbr/src/meshlet/from_mesh.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index 9db2f3a72d17b..102de57597c4e 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -115,7 +115,7 @@ impl MeshletMesh { &group_meshlets, &mut group_error, &mut bounding_spheres, - &simplification_errors, + &mut simplification_errors, ); // Build new meshlets using the simplified group @@ -334,14 +334,14 @@ fn compute_lod_group_data( group_meshlets: &[usize], group_error: &mut f32, bounding_spheres: &mut [MeshletBoundingSpheres], - simplification_errors: &[MeshletSimplificationError], + simplification_errors: &mut [MeshletSimplificationError], ) -> MeshletBoundingSphere { let mut group_bounding_sphere = MeshletBoundingSphere { center: Vec3::ZERO, radius: 0.0, }; - // Compute the group lod sphere center as a weighted average of the children spheres + // Compute the lod group sphere center as a weighted average of the children spheres let mut weight = 0.0; for meshlet_id in group_meshlets { let meshlet_lod_bounding_sphere = bounding_spheres[*meshlet_id].lod_group_sphere; @@ -367,12 +367,15 @@ fn compute_lod_group_data( .max(meshlet_lod_bounding_sphere.radius + d); } + // Force parent error to be >= child error (we're currently building the parent from its children) for meshlet_id in group_meshlets { - // Force parent error to be >= child error (we're currently building the parent from its children) *group_error = group_error.max(simplification_errors[*meshlet_id].group_error); + } - // Set the children's parent group lod sphere to the new sphere we made for this group + // Set the children's lod parent group data to the new lod group we just made + for meshlet_id in group_meshlets { bounding_spheres[*meshlet_id].lod_parent_group_sphere = group_bounding_sphere; + simplification_errors[*meshlet_id].parent_group_error = *group_error; } group_bounding_sphere From 1ddf17304ea69c409008f8caae188ced68677c11 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Thu, 10 Oct 2024 00:41:01 -0700 Subject: [PATCH 04/29] Misc --- crates/bevy_pbr/src/meshlet/cull_clusters.wgsl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl index 320c0006333a8..74074cc770668 100644 --- a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl +++ b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl @@ -14,6 +14,7 @@ meshlet_hardware_raster_indirect_args, meshlet_raster_clusters, meshlet_raster_cluster_rightmost_slot, + MeshletBoundingSphere, } #import bevy_render::maths::affine3_to_square @@ -63,9 +64,8 @@ fn cull_clusters( // Check LOD cut (cluster group error imperceptible, and parent group error not imperceptible) let simplification_errors = meshlet_simplification_errors[meshlet_id]; - let lod_is_ok = lod_error_is_imperceptible(bounding_spheres.lod_group_sphere.center, bounding_spheres.lod_group_sphere.radius, simplification_errors.group_error, world_from_local, world_scale); - let parent_lod_is_ok = lod_error_is_imperceptible(bounding_spheres.lod_parent_group_sphere.center, bounding_spheres.lod_parent_group_sphere.radius, simplification_errors.parent_group_error, world_from_local, world_scale); - if !lod_is_ok || parent_lod_is_ok { return; } + if !lod_error_is_imperceptible(bounding_spheres.lod_group_sphere, simplification_errors.group_error, world_from_local, world_scale) { return; } + if lod_error_is_imperceptible(bounding_spheres.lod_parent_group_sphere, simplification_errors.parent_group_error, world_from_local, world_scale) { return; } #endif // Project the culling bounding sphere to view-space for occlusion culling @@ -140,15 +140,15 @@ fn cull_clusters( meshlet_raster_clusters[buffer_slot] = cluster_id; } -fn lod_error_is_imperceptible(cp: vec3, r: f32, error: f32, world_from_local: mat4x4, world_scale: f32) -> bool { - let cp_world = world_from_local * vec4(cp, 1.0); - let r_view = world_scale * r; +fn lod_error_is_imperceptible(sphere: MeshletBoundingSphere, error: f32, world_from_local: mat4x4, world_scale: f32) -> bool { + let cp_world = world_from_local * vec4(sphere.center, 1.0); + let r_view = world_scale * sphere.radius; let cp_view = (view.view_from_world * vec4(cp_world.xyz, 1.0)).xyz; // TODO: Handle view clipping / being inside sphere bounds let aabb = project_view_space_sphere_to_screen_space_aabb(cp_view, r_view); let screen_size = max(aabb.z - aabb.x, aabb.w - aabb.y); - let meters_per_pixel = r / screen_size; + let meters_per_pixel = sphere.radius / screen_size; return error < meters_per_pixel; } From 8b71b243ca51be1a59a7c40d9fbfd7a5083f04e7 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Thu, 10 Oct 2024 19:04:05 -0700 Subject: [PATCH 05/29] Use zeux's error projection code --- .../bevy_pbr/src/meshlet/cull_clusters.wgsl | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl index 74074cc770668..52413ebf7119c 100644 --- a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl +++ b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl @@ -140,17 +140,22 @@ fn cull_clusters( meshlet_raster_clusters[buffer_slot] = cluster_id; } -fn lod_error_is_imperceptible(sphere: MeshletBoundingSphere, error: f32, world_from_local: mat4x4, world_scale: f32) -> bool { - let cp_world = world_from_local * vec4(sphere.center, 1.0); - let r_view = world_scale * sphere.radius; - let cp_view = (view.view_from_world * vec4(cp_world.xyz, 1.0)).xyz; - - // TODO: Handle view clipping / being inside sphere bounds - let aabb = project_view_space_sphere_to_screen_space_aabb(cp_view, r_view); - let screen_size = max(aabb.z - aabb.x, aabb.w - aabb.y); - let meters_per_pixel = sphere.radius / screen_size; +// TODO: This doesn't account for perspective and other edge cases as well as Nanite does +// https://github.com/zeux/meshoptimizer/blob/1e48e96c7e8059321de492865165e9ef071bffba/demo/nanite.cpp#L115 +fn lod_error_is_imperceptible(lod_sphere: MeshletBoundingSphere, simplification_error: f32, world_from_local: mat4x4, world_scale: f32) -> bool { + let sphere_world_space = (world_from_local * vec4(lod_sphere.center, 1.0)).xyz; + let radius_world_space = world_scale * lod_sphere.radius; + let error_world_space = world_scale * simplification_error; + + let distance_to_closest_point_on_sphere = distance(sphere_world_space, view.world_position) - radius_world_space; + let distance_to_closest_point_on_sphere_clamped_to_znear = max(distance_to_closest_point_on_sphere, view.clip_from_view[3][2]); + var projected_error = error_world_space / distance_to_closest_point_on_sphere_clamped_to_znear; + if view.clip_from_view[3][3] != 1.0 { + projected_error *= view.clip_from_view[1][1]; + } + projected_error *= view.viewport.w; - return error < meters_per_pixel; + return projected_error < 1.0; } // https://zeux.io/2023/01/12/approximate-projected-bounds From 4f96881994f0a415ccc41fde9d0cd216849cae53 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Thu, 10 Oct 2024 19:23:26 -0700 Subject: [PATCH 06/29] Misc --- crates/bevy_pbr/src/meshlet/from_mesh.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index 102de57597c4e..e12e9c573fda8 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -349,10 +349,7 @@ fn compute_lod_group_data( meshlet_lod_bounding_sphere.center * meshlet_lod_bounding_sphere.radius; weight += meshlet_lod_bounding_sphere.radius; } - // TODO: Check needed? - // if weight > 0.0 { group_bounding_sphere.center /= weight; - // } // Force parent group sphere to contain all child group spheres (we're currently building the parent from its children) // TODO: This does not produce the absolute minimal bounding sphere. Doing so is non-trivial. From cac11bc59ac6e45474ab3d06fda1052e338f2ea4 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Thu, 10 Oct 2024 20:00:14 -0700 Subject: [PATCH 07/29] Pack group errors 2x16float --- crates/bevy_pbr/Cargo.toml | 11 ++++++----- crates/bevy_pbr/src/meshlet/asset.rs | 8 ++++---- crates/bevy_pbr/src/meshlet/cull_clusters.wgsl | 6 +++--- crates/bevy_pbr/src/meshlet/from_mesh.rs | 13 +++++++------ crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl | 7 +------ 5 files changed, 21 insertions(+), 24 deletions(-) diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index 72b2b53938bd9..67552945fc87e 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -18,7 +18,7 @@ shader_format_glsl = ["bevy_render/shader_format_glsl"] trace = ["bevy_render/trace"] ios_simulator = ["bevy_render/ios_simulator"] # Enables the meshlet renderer for dense high-poly scenes (experimental) -meshlet = ["dep:lz4_flex", "dep:range-alloc", "dep:bevy_tasks"] +meshlet = ["dep:lz4_flex", "dep:range-alloc", "dep:half", "dep:bevy_tasks"] # Enables processing meshes into meshlet meshes meshlet_processor = [ "meshlet", @@ -50,16 +50,17 @@ bevy_window = { path = "../bevy_window", version = "0.15.0-dev" } # other bitflags = "2.3" fixedbitset = "0.5" -# meshlet -lz4_flex = { version = "0.11", default-features = false, features = [ - "frame", -], optional = true } derive_more = { version = "1", default-features = false, features = [ "error", "from", "display", ] } +# meshlet +lz4_flex = { version = "0.11", default-features = false, features = [ + "frame", +], optional = true } range-alloc = { version = "0.1.3", optional = true } +half = { version = "2", features = ["bytemuck"], optional = true } meshopt = { version = "0.3.0", optional = true } metis = { version = "0.2", optional = true } itertools = { version = "0.13", optional = true } diff --git a/crates/bevy_pbr/src/meshlet/asset.rs b/crates/bevy_pbr/src/meshlet/asset.rs index 85226aeb4b8bb..6050ef6d4456f 100644 --- a/crates/bevy_pbr/src/meshlet/asset.rs +++ b/crates/bevy_pbr/src/meshlet/asset.rs @@ -9,6 +9,7 @@ use bevy_reflect::TypePath; use bevy_tasks::block_on; use bytemuck::{Pod, Zeroable}; use derive_more::derive::{Display, Error, From}; +use half::f16; use lz4_flex::frame::{FrameDecoder, FrameEncoder}; use std::io::{Read, Write}; @@ -51,7 +52,7 @@ pub struct MeshletMesh { pub(crate) meshlets: Arc<[Meshlet]>, /// Spherical bounding volumes. pub(crate) meshlet_bounding_spheres: Arc<[MeshletBoundingSpheres]>, - /// Meshlet simplification errors. + /// Meshlet group and parent group simplification errors. pub(crate) meshlet_simplification_errors: Arc<[MeshletSimplificationError]>, } @@ -109,14 +110,13 @@ pub struct MeshletBoundingSphere { } /// Simplification error used for choosing level of detail for a [`Meshlet`]. -// TODO: pack2x16float #[derive(Copy, Clone, Pod, Zeroable)] #[repr(C)] pub struct MeshletSimplificationError { /// Simplification error used for determining if this meshlet's group is at the correct level of detail for a given view. - pub group_error: f32, + pub group_error: f16, /// Simplification error used for determining if this meshlet's parent group is at the correct level of detail for a given view. - pub parent_group_error: f32, + pub parent_group_error: f16, } /// An [`AssetSaver`] for `.meshlet_mesh` [`MeshletMesh`] assets. diff --git a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl index 52413ebf7119c..beafac5288f94 100644 --- a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl +++ b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl @@ -63,9 +63,9 @@ fn cull_clusters( } // Check LOD cut (cluster group error imperceptible, and parent group error not imperceptible) - let simplification_errors = meshlet_simplification_errors[meshlet_id]; - if !lod_error_is_imperceptible(bounding_spheres.lod_group_sphere, simplification_errors.group_error, world_from_local, world_scale) { return; } - if lod_error_is_imperceptible(bounding_spheres.lod_parent_group_sphere, simplification_errors.parent_group_error, world_from_local, world_scale) { return; } + let simplification_errors = unpack2x16float(meshlet_simplification_errors[meshlet_id]); + if !lod_error_is_imperceptible(bounding_spheres.lod_group_sphere, simplification_errors.x, world_from_local, world_scale) { return; } + if lod_error_is_imperceptible(bounding_spheres.lod_parent_group_sphere, simplification_errors.y, world_from_local, world_scale) { return; } #endif // Project the culling bounding sphere to view-space for occlusion culling diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index e12e9c573fda8..f4d97bbd43cc5 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -11,6 +11,7 @@ use bevy_utils::HashMap; use bitvec::{order::Lsb0, vec::BitVec, view::BitView}; use core::ops::Range; use derive_more::derive::{Display, Error}; +use half::f16; use itertools::Itertools; use meshopt::{ build_meshlets, ffi::meshopt_Meshlet, simplify, Meshlets, SimplifyOptions, VertexDataAdapter, @@ -80,8 +81,8 @@ impl MeshletMesh { }) .collect::>(); let mut simplification_errors = iter::repeat(MeshletSimplificationError { - group_error: 0.0, - parent_group_error: f32::MAX, + group_error: f16::ZERO, + parent_group_error: f16::MAX, }) .take(meshlets.len()) .collect::>(); @@ -140,7 +141,7 @@ impl MeshletMesh { simplification_errors.extend( iter::repeat(MeshletSimplificationError { group_error, - parent_group_error: f32::MAX, + parent_group_error: f16::MAX, }) .take(new_meshlet_ids.len()), ); @@ -300,7 +301,7 @@ fn simplify_meshlet_group( group_meshlets: &[usize], meshlets: &Meshlets, vertices: &VertexDataAdapter<'_>, -) -> Option<(Vec, f32)> { +) -> Option<(Vec, f16)> { // Build a new index buffer into the mesh vertex data by combining all meshlet data in the group let mut group_indices = Vec::new(); for meshlet_id in group_meshlets { @@ -327,12 +328,12 @@ fn simplify_meshlet_group( return None; } - Some((simplified_group_indices, error)) + Some((simplified_group_indices, f16::from_f32(error))) } fn compute_lod_group_data( group_meshlets: &[usize], - group_error: &mut f32, + group_error: &mut f16, bounding_spheres: &mut [MeshletBoundingSpheres], simplification_errors: &mut [MeshletSimplificationError], ) -> MeshletBoundingSphere { diff --git a/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl b/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl index 5e5b9d139f576..42b322d731a80 100644 --- a/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl +++ b/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl @@ -35,11 +35,6 @@ struct MeshletBoundingSphere { radius: f32, } -struct MeshletSimplificationError { - group_error: f32, - parent_group_error: f32, -} - struct DispatchIndirectArgs { x: atomic, y: u32, @@ -67,7 +62,7 @@ var cluster_count: u32; var meshlet_raster_cluster_rightmost_slot: u32; @group(0) @binding(0) var meshlet_cluster_meshlet_ids: array; // Per cluster @group(0) @binding(1) var meshlet_bounding_spheres: array; // Per meshlet -@group(0) @binding(2) var meshlet_simplification_errors: array; // Per meshlet +@group(0) @binding(2) var meshlet_simplification_errors: array; // Per meshlet @group(0) @binding(3) var meshlet_cluster_instance_ids: array; // Per cluster @group(0) @binding(4) var meshlet_instance_uniforms: array; // Per entity instance @group(0) @binding(5) var meshlet_view_instance_visibility: array; // 1 bit per entity instance, packed as a bitmask From b5d8a0ce03ecec37ddd20fd466ae7e05072b8e78 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Thu, 10 Oct 2024 20:16:31 -0700 Subject: [PATCH 08/29] Clippy --- crates/bevy_pbr/src/meshlet/from_mesh.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index f4d97bbd43cc5..8baa72388a0b7 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -9,7 +9,7 @@ use bevy_render::{ }; use bevy_utils::HashMap; use bitvec::{order::Lsb0, vec::BitVec, view::BitView}; -use core::ops::Range; +use core::{iter, ops::Range}; use derive_more::derive::{Display, Error}; use half::f16; use itertools::Itertools; @@ -18,7 +18,6 @@ use meshopt::{ }; use metis::Graph; use smallvec::SmallVec; -use std::iter; /// Default vertex position quantization factor for use with [`MeshletMesh::from_mesh`]. /// From 634eaa283807ff2d8a6ba98e8f32953f7c125810 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Fri, 11 Oct 2024 21:36:16 -0700 Subject: [PATCH 09/29] Try to fix ortho, use 0.5px threshold --- crates/bevy_pbr/src/meshlet/cull_clusters.wgsl | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl index beafac5288f94..fb47451ff175c 100644 --- a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl +++ b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl @@ -147,15 +147,19 @@ fn lod_error_is_imperceptible(lod_sphere: MeshletBoundingSphere, simplification_ let radius_world_space = world_scale * lod_sphere.radius; let error_world_space = world_scale * simplification_error; - let distance_to_closest_point_on_sphere = distance(sphere_world_space, view.world_position) - radius_world_space; - let distance_to_closest_point_on_sphere_clamped_to_znear = max(distance_to_closest_point_on_sphere, view.clip_from_view[3][2]); - var projected_error = error_world_space / distance_to_closest_point_on_sphere_clamped_to_znear; + var projected_error = error_world_space; if view.clip_from_view[3][3] != 1.0 { - projected_error *= view.clip_from_view[1][1]; + // Perspective + let distance_to_closest_point_on_sphere = distance(sphere_world_space, view.world_position) - radius_world_space; + let distance_to_closest_point_on_sphere_clamped_to_znear = max(distance_to_closest_point_on_sphere, view.clip_from_view[3][2]); + projected_error *= view.clip_from_view[1][1] / distance_to_closest_point_on_sphere_clamped_to_znear; + } else { + // Orthographic + projected_error *= view.clip_from_view[1][1] * 0.5; } projected_error *= view.viewport.w; - return projected_error < 1.0; + return projected_error < 0.5; } // https://zeux.io/2023/01/12/approximate-projected-bounds From b093698d98e85e213821f75b12ec01d14abc6d99 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Fri, 11 Oct 2024 22:23:12 -0700 Subject: [PATCH 10/29] Fixes for error projection --- crates/bevy_pbr/src/meshlet/cull_clusters.wgsl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl index fb47451ff175c..41c58794e3db2 100644 --- a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl +++ b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl @@ -152,14 +152,12 @@ fn lod_error_is_imperceptible(lod_sphere: MeshletBoundingSphere, simplification_ // Perspective let distance_to_closest_point_on_sphere = distance(sphere_world_space, view.world_position) - radius_world_space; let distance_to_closest_point_on_sphere_clamped_to_znear = max(distance_to_closest_point_on_sphere, view.clip_from_view[3][2]); - projected_error *= view.clip_from_view[1][1] / distance_to_closest_point_on_sphere_clamped_to_znear; - } else { - // Orthographic - projected_error *= view.clip_from_view[1][1] * 0.5; + projected_error /= distance_to_closest_point_on_sphere_clamped_to_znear; } + projected_error *= view.clip_from_view[1][1] * 0.5; projected_error *= view.viewport.w; - return projected_error < 0.5; + return projected_error < 1.0; } // https://zeux.io/2023/01/12/approximate-projected-bounds From 31dd6f60c7c31ba500271cb97804d0cfec1348fd Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Fri, 11 Oct 2024 23:37:02 -0700 Subject: [PATCH 11/29] Go back to previous codegen, slightly faster --- crates/bevy_pbr/src/meshlet/cull_clusters.wgsl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl index 41c58794e3db2..8ff42bced00d8 100644 --- a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl +++ b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl @@ -64,8 +64,9 @@ fn cull_clusters( // Check LOD cut (cluster group error imperceptible, and parent group error not imperceptible) let simplification_errors = unpack2x16float(meshlet_simplification_errors[meshlet_id]); - if !lod_error_is_imperceptible(bounding_spheres.lod_group_sphere, simplification_errors.x, world_from_local, world_scale) { return; } - if lod_error_is_imperceptible(bounding_spheres.lod_parent_group_sphere, simplification_errors.y, world_from_local, world_scale) { return; } + let lod_is_ok = lod_error_is_imperceptible(bounding_spheres.lod_group_sphere, simplification_errors.x, world_from_local, world_scale); + let parent_lod_is_ok = lod_error_is_imperceptible(bounding_spheres.lod_parent_group_sphere, simplification_errors.y, world_from_local, world_scale); + if !lod_is_ok || parent_lod_is_ok { return; } #endif // Project the culling bounding sphere to view-space for occlusion culling From 3a48456859f6c735003aebdbd267634f3ac26c53 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Fri, 11 Oct 2024 23:43:17 -0700 Subject: [PATCH 12/29] Update bunny URL --- Cargo.toml | 2 +- examples/3d/meshlet.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1f548f505c294..f92367630c188 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1209,7 +1209,7 @@ setup = [ "curl", "-o", "assets/models/bunny.meshlet_mesh", - "https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/8443bbdee0bf517e6c297dede7f6a46ab712ee4c/bunny.meshlet_mesh", + "https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/167cdaf0b08f89fb747b83b94c27755f116cd408/bunny.meshlet_mesh", ], ] diff --git a/examples/3d/meshlet.rs b/examples/3d/meshlet.rs index 72da8c968b4a5..7f425b40eb8ae 100644 --- a/examples/3d/meshlet.rs +++ b/examples/3d/meshlet.rs @@ -17,7 +17,7 @@ use camera_controller::{CameraController, CameraControllerPlugin}; use std::{f32::consts::PI, path::Path, process::ExitCode}; const ASSET_URL: &str = - "https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/8443bbdee0bf517e6c297dede7f6a46ab712ee4c/bunny.meshlet_mesh"; + "https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/167cdaf0b08f89fb747b83b94c27755f116cd408/bunny.meshlet_mesh"; fn main() -> ExitCode { if !Path::new("./assets/models/bunny.meshlet_mesh").exists() { From 27c03f87c2a7ccedc4c6f3af1425e753c71f20f1 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Sat, 12 Oct 2024 14:52:38 -0700 Subject: [PATCH 13/29] Add missing doc comment --- crates/bevy_pbr/src/meshlet/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_pbr/src/meshlet/mod.rs b/crates/bevy_pbr/src/meshlet/mod.rs index 80d5f7aa59bfc..1deb4ae85c762 100644 --- a/crates/bevy_pbr/src/meshlet/mod.rs +++ b/crates/bevy_pbr/src/meshlet/mod.rs @@ -290,6 +290,7 @@ impl Plugin for MeshletPlugin { } } +/// The meshlet mesh equivalent of [`bevy_render::mesh::Mesh3d`]. #[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)] #[reflect(Component, Default)] #[require(Transform, Visibility)] From d9f5b7512994da87696a24b646eae5fea5708a1d Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:00:59 -0700 Subject: [PATCH 14/29] Remove perspective comment, I don't actually like how it behaves --- crates/bevy_pbr/src/meshlet/cull_clusters.wgsl | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl index 8ff42bced00d8..442a57824e551 100644 --- a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl +++ b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl @@ -141,7 +141,6 @@ fn cull_clusters( meshlet_raster_clusters[buffer_slot] = cluster_id; } -// TODO: This doesn't account for perspective and other edge cases as well as Nanite does // https://github.com/zeux/meshoptimizer/blob/1e48e96c7e8059321de492865165e9ef071bffba/demo/nanite.cpp#L115 fn lod_error_is_imperceptible(lod_sphere: MeshletBoundingSphere, simplification_error: f32, world_from_local: mat4x4, world_scale: f32) -> bool { let sphere_world_space = (world_from_local * vec4(lod_sphere.center, 1.0)).xyz; From 2fa97310d801a30e4c7eb1b00c059600da33f32d Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:03:14 -0700 Subject: [PATCH 15/29] Larger meshlet group size, constant seed for metis, smallvec optimization --- crates/bevy_pbr/src/meshlet/from_mesh.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index 8baa72388a0b7..a763b32dac25b 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -24,6 +24,7 @@ use smallvec::SmallVec; /// Snaps vertices to the nearest 1/16th of a centimeter (1/2^4). pub const DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR: u8 = 4; +const TARGET_MESHLETS_PER_GROUP: usize = 8; const MESHLET_VERTEX_SIZE_IN_BYTES: usize = 32; const CENTIMETERS_PER_METER: f32 = 100.0; @@ -265,7 +266,7 @@ fn find_connected_meshlets( fn group_meshlets( simplification_queue: Range, connected_meshlets_per_meshlet: &[Vec<(usize, usize)>], -) -> Vec> { +) -> Vec> { let mut xadj = Vec::with_capacity(simplification_queue.len() + 1); let mut adjncy = Vec::new(); let mut adjwgt = Vec::new(); @@ -281,15 +282,17 @@ fn group_meshlets( xadj.push(adjncy.len() as i32); let mut group_per_meshlet = vec![0; simplification_queue.len()]; - let partition_count = simplification_queue.len().div_ceil(4); // TODO: Nanite uses groups of 8-32, probably based on some kind of heuristic + let partition_count = simplification_queue + .len() + .div_ceil(TARGET_MESHLETS_PER_GROUP); // TODO: Nanite uses groups of 8-32, probably based on some kind of heuristic Graph::new(1, partition_count as i32, &xadj, &adjncy) .unwrap() + .set_option(metis::option::Seed(17)) .set_adjwgt(&adjwgt) .part_kway(&mut group_per_meshlet) .unwrap(); - let mut groups = vec![Vec::new(); partition_count]; - + let mut groups = vec![SmallVec::new(); partition_count]; for (i, meshlet_group) in group_per_meshlet.into_iter().enumerate() { groups[meshlet_group as usize].push(i + simplification_queue.start); } From a88a5a7aaaff5b9799e41dd13e0b28e835ea1979 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:03:15 -0700 Subject: [PATCH 16/29] Factor out constant --- crates/bevy_pbr/src/meshlet/from_mesh.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index a763b32dac25b..cc7840d0f9b6c 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -24,10 +24,13 @@ use smallvec::SmallVec; /// Snaps vertices to the nearest 1/16th of a centimeter (1/2^4). pub const DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR: u8 = 4; -const TARGET_MESHLETS_PER_GROUP: usize = 8; const MESHLET_VERTEX_SIZE_IN_BYTES: usize = 32; const CENTIMETERS_PER_METER: f32 = 100.0; +const TARGET_MESHLETS_PER_GROUP: usize = 8; +// Reject groups that keep at least 95% of their original triangles +const SIMPLIFICATION_FAILURE_PERCENTAGE: f32 = 0.95; + impl MeshletMesh { /// Process a [`Mesh`] to generate a [`MeshletMesh`]. /// @@ -325,8 +328,10 @@ fn simplify_meshlet_group( Some(&mut error), ); - // Check if we were able to simplify at least a little (95% of the original triangle count) - if simplified_group_indices.len() as f32 / group_indices.len() as f32 > 0.95 { + // Check if we were able to simplify at least a little + if simplified_group_indices.len() as f32 / group_indices.len() as f32 + > SIMPLIFICATION_FAILURE_PERCENTAGE + { return None; } From 8db943e244f4bda9ea43d5bde68c1dcdecae35ef Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:03:15 -0700 Subject: [PATCH 17/29] Misc refactor --- crates/bevy_pbr/src/meshlet/from_mesh.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index cc7840d0f9b6c..71fa70351cc16 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -19,18 +19,18 @@ use meshopt::{ use metis::Graph; use smallvec::SmallVec; +// Aim to have 8 meshlets per group +const TARGET_MESHLETS_PER_GROUP: usize = 8; +// Reject groups that keep at least 95% of their original triangles +const SIMPLIFICATION_FAILURE_PERCENTAGE: f32 = 0.95; + /// Default vertex position quantization factor for use with [`MeshletMesh::from_mesh`]. /// /// Snaps vertices to the nearest 1/16th of a centimeter (1/2^4). pub const DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR: u8 = 4; -const MESHLET_VERTEX_SIZE_IN_BYTES: usize = 32; const CENTIMETERS_PER_METER: f32 = 100.0; -const TARGET_MESHLETS_PER_GROUP: usize = 8; -// Reject groups that keep at least 95% of their original triangles -const SIMPLIFICATION_FAILURE_PERCENTAGE: f32 = 0.95; - impl MeshletMesh { /// Process a [`Mesh`] to generate a [`MeshletMesh`]. /// @@ -163,6 +163,7 @@ impl MeshletMesh { meshlet, meshlets.get(i).vertices, &vertex_buffer, + vertex_stride, &mut vertex_positions, &mut vertex_normals, &mut vertex_uvs, @@ -418,6 +419,7 @@ fn build_and_compress_meshlet_vertex_data( meshlet: &meshopt_Meshlet, meshlet_vertex_ids: &[u32], vertex_buffer: &[u8], + vertex_stride: usize, vertex_positions: &mut BitVec, vertex_normals: &mut Vec, vertex_uvs: &mut Vec, @@ -437,9 +439,8 @@ fn build_and_compress_meshlet_vertex_data( let mut quantized_positions = [IVec3::ZERO; 255]; for (i, vertex_id) in meshlet_vertex_ids.iter().enumerate() { // Load source vertex attributes - let vertex_id_byte = *vertex_id as usize * MESHLET_VERTEX_SIZE_IN_BYTES; - let vertex_data = - &vertex_buffer[vertex_id_byte..(vertex_id_byte + MESHLET_VERTEX_SIZE_IN_BYTES)]; + let vertex_id_byte = *vertex_id as usize * vertex_stride; + let vertex_data = &vertex_buffer[vertex_id_byte..(vertex_id_byte + vertex_stride)]; let position = Vec3::from_slice(bytemuck::cast_slice(&vertex_data[0..12])); let normal = Vec3::from_slice(bytemuck::cast_slice(&vertex_data[12..24])); let uv = Vec2::from_slice(bytemuck::cast_slice(&vertex_data[24..32])); From 0abbb47d7c8bb543def2657fb1930a9972fe3d7d Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:03:15 -0700 Subject: [PATCH 18/29] Small rename --- crates/bevy_pbr/src/meshlet/from_mesh.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index 71fa70351cc16..474fe27ccc597 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -159,7 +159,7 @@ impl MeshletMesh { let mut vertex_uvs = Vec::new(); let mut bevy_meshlets = Vec::with_capacity(meshlets.len()); for (i, meshlet) in meshlets.meshlets.iter().enumerate() { - build_and_compress_meshlet_vertex_data( + build_and_compress_per_meshlet_vertex_data( meshlet, meshlets.get(i).vertices, &vertex_buffer, @@ -415,7 +415,7 @@ fn split_simplified_group_into_new_meshlets( } #[allow(clippy::too_many_arguments)] -fn build_and_compress_meshlet_vertex_data( +fn build_and_compress_per_meshlet_vertex_data( meshlet: &meshopt_Meshlet, meshlet_vertex_ids: &[u32], vertex_buffer: &[u8], From a1c2ee371f23b3399a57ff9f1e47db61c9b464dd Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:03:15 -0700 Subject: [PATCH 19/29] Use position-only vertices instead of triangle edges to determine meshlet connectivity --- crates/bevy_pbr/src/meshlet/from_mesh.rs | 74 +++++++++++++----------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index 474fe27ccc597..e626d12eec582 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -14,7 +14,8 @@ use derive_more::derive::{Display, Error}; use half::f16; use itertools::Itertools; use meshopt::{ - build_meshlets, ffi::meshopt_Meshlet, simplify, Meshlets, SimplifyOptions, VertexDataAdapter, + build_meshlets, ffi::meshopt_Meshlet, generate_vertex_remap_multi, simplify, Meshlets, + SimplifyOptions, VertexDataAdapter, VertexStream, }; use metis::Graph; use smallvec::SmallVec; @@ -90,14 +91,27 @@ impl MeshletMesh { .take(meshlets.len()) .collect::>(); + // Generate a position-only vertex buffer for determining what meshlets are connected for use in grouping + let (_, position_only_vertex_remap) = generate_vertex_remap_multi( + vertices.vertex_count, + &[VertexStream::new_with_stride::( + &vertex_buffer, + vertex_stride, + )], + Some(&indices), + ); + // Build further LODs let mut simplification_queue = 0..meshlets.len(); while simplification_queue.len() > 1 { - // For each meshlet build a list of connected meshlets (meshlets that share a triangle edge) - let connected_meshlets_per_meshlet = - find_connected_meshlets(simplification_queue.clone(), &meshlets); + // For each meshlet build a list of connected meshlets (meshlets that share a vertex) + let connected_meshlets_per_meshlet = find_connected_meshlets( + simplification_queue.clone(), + &meshlets, + &position_only_vertex_remap, + ); - // Group meshlets into roughly groups of 4, grouping meshlets with a high number of shared edges + // Group meshlets into roughly groups of 4, grouping meshlets with a high number of shared vertices // http://glaros.dtc.umn.edu/gkhome/fetch/sw/metis/manual.pdf let groups = group_meshlets( simplification_queue.clone(), @@ -212,47 +226,38 @@ fn compute_meshlets(indices: &[u32], vertices: &VertexDataAdapter) -> Meshlets { fn find_connected_meshlets( simplification_queue: Range, meshlets: &Meshlets, + position_only_vertex_remap: &[u32], ) -> Vec> { - // For each edge, gather all meshlets that use it - let mut edges_to_meshlets = HashMap::new(); - + // For each vertex, build a list of all meshlets that use it + let mut vertices_to_meshlets = vec![Vec::new(); position_only_vertex_remap.len()]; for meshlet_id in simplification_queue.clone() { let meshlet = meshlets.get(meshlet_id); - for i in meshlet.triangles.chunks(3) { - for k in 0..3 { - let v0 = meshlet.vertices[i[k] as usize]; - let v1 = meshlet.vertices[i[(k + 1) % 3] as usize]; - let edge = (v0.min(v1), v0.max(v1)); - - let vec = edges_to_meshlets - .entry(edge) - .or_insert_with(SmallVec::<[usize; 2]>::new); - // Meshlets are added in order, so we can just check the last element to deduplicate, - // in the case of two triangles sharing the same edge within a single meshlet - if vec.last() != Some(&meshlet_id) { - vec.push(meshlet_id); - } + for index in meshlet.triangles { + let vertex_id = position_only_vertex_remap[meshlet.vertices[*index as usize] as usize]; + let vertex_to_meshlets = &mut vertices_to_meshlets[vertex_id as usize]; + // Meshlets are added in order, so we can just check the last element to deduplicate, + // in the case of two triangles sharing the same vertex within a single meshlet + if vertex_to_meshlets.last() != Some(&meshlet_id) { + vertex_to_meshlets.push(meshlet_id); } } } - // For each meshlet pair, count how many edges they share - let mut shared_edge_count = HashMap::new(); - - for (_, meshlet_ids) in edges_to_meshlets { - for (meshlet_id1, meshlet_id2) in meshlet_ids.into_iter().tuple_combinations() { - let count = shared_edge_count + // For each meshlet pair, count how many vertices they share + let mut meshlet_pair_to_shared_vertex_count = HashMap::new(); + for vertex_meshlet_ids in vertices_to_meshlets { + for (meshlet_id1, meshlet_id2) in vertex_meshlet_ids.into_iter().tuple_combinations() { + let count = meshlet_pair_to_shared_vertex_count .entry((meshlet_id1.min(meshlet_id2), meshlet_id1.max(meshlet_id2))) .or_insert(0); *count += 1; } } - // For each meshlet, gather all meshlets that share at least one edge along with shared edge count + // For each meshlet, gather all other meshlets that share at least one vertex along with their shared vertex count let mut connected_meshlets = vec![Vec::new(); simplification_queue.len()]; - - for ((meshlet_id1, meshlet_id2), shared_count) in shared_edge_count { - // We record id1->id2 and id2->id1 as adjacency is symmetrical + for ((meshlet_id1, meshlet_id2), shared_count) in meshlet_pair_to_shared_vertex_count { + // We record both id1->id2 and id2->id1 as adjacency is symmetrical connected_meshlets[meshlet_id1 - simplification_queue.start] .push((meshlet_id2, shared_count)); connected_meshlets[meshlet_id2 - simplification_queue.start] @@ -276,11 +281,12 @@ fn group_meshlets( let mut adjwgt = Vec::new(); for meshlet_id in simplification_queue.clone() { xadj.push(adjncy.len() as i32); - for (connected_meshlet_id, shared_edge_count) in + for (connected_meshlet_id, shared_vertex_count) in connected_meshlets_per_meshlet[meshlet_id - simplification_queue.start].iter() { adjncy.push((connected_meshlet_id - simplification_queue.start) as i32); - adjwgt.push(*shared_edge_count as i32); + adjwgt.push(*shared_vertex_count as i32); + // TODO: Additional weight based on meshlet spatial proximity } } xadj.push(adjncy.len() as i32); From 62d3605e31411375ee0f9c2d44d04f4f65da730a Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:03:15 -0700 Subject: [PATCH 20/29] Misc --- crates/bevy_pbr/src/meshlet/from_mesh.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index e626d12eec582..a060f9d575c61 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -111,8 +111,8 @@ impl MeshletMesh { &position_only_vertex_remap, ); - // Group meshlets into roughly groups of 4, grouping meshlets with a high number of shared vertices - // http://glaros.dtc.umn.edu/gkhome/fetch/sw/metis/manual.pdf + // Group meshlets into roughly groups of size TARGET_MESHLETS_PER_GROUP, + // grouping meshlets with a high number of shared vertices let groups = group_meshlets( simplification_queue.clone(), &connected_meshlets_per_meshlet, @@ -272,6 +272,7 @@ fn find_connected_meshlets( connected_meshlets } +// METIS manual: http://glaros.dtc.umn.edu/gkhome/fetch/sw/metis/manual.pdf fn group_meshlets( simplification_queue: Range, connected_meshlets_per_meshlet: &[Vec<(usize, usize)>], From 99722b07a9a5a7b809eb71ddb9df7dddcf78e958 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:03:15 -0700 Subject: [PATCH 21/29] Use position-only vertex remap count to save a bit of memory when allocating --- crates/bevy_pbr/src/meshlet/from_mesh.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index a060f9d575c61..d67ba28d9f5e5 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -92,7 +92,7 @@ impl MeshletMesh { .collect::>(); // Generate a position-only vertex buffer for determining what meshlets are connected for use in grouping - let (_, position_only_vertex_remap) = generate_vertex_remap_multi( + let (position_only_vertex_count, position_only_vertex_remap) = generate_vertex_remap_multi( vertices.vertex_count, &[VertexStream::new_with_stride::( &vertex_buffer, @@ -109,6 +109,7 @@ impl MeshletMesh { simplification_queue.clone(), &meshlets, &position_only_vertex_remap, + position_only_vertex_count as usize, ); // Group meshlets into roughly groups of size TARGET_MESHLETS_PER_GROUP, @@ -227,9 +228,10 @@ fn find_connected_meshlets( simplification_queue: Range, meshlets: &Meshlets, position_only_vertex_remap: &[u32], + position_only_vertex_count: usize, ) -> Vec> { // For each vertex, build a list of all meshlets that use it - let mut vertices_to_meshlets = vec![Vec::new(); position_only_vertex_remap.len()]; + let mut vertices_to_meshlets = vec![Vec::new(); position_only_vertex_count]; for meshlet_id in simplification_queue.clone() { let meshlet = meshlets.get(meshlet_id); for index in meshlet.triangles { From 86f0b7089df1085d42c0ed07f2f333aabe836ed6 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:03:15 -0700 Subject: [PATCH 22/29] Fix segfault when generating vertex remap --- crates/bevy_pbr/src/meshlet/from_mesh.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index d67ba28d9f5e5..ba033b8771502 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -95,7 +95,7 @@ impl MeshletMesh { let (position_only_vertex_count, position_only_vertex_remap) = generate_vertex_remap_multi( vertices.vertex_count, &[VertexStream::new_with_stride::( - &vertex_buffer, + vertex_buffer.as_ptr(), vertex_stride, )], Some(&indices), From 24187a470236faab1cdbc22e418b9fc8190d9867 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Sat, 12 Oct 2024 21:57:23 -0700 Subject: [PATCH 23/29] Lock group borders manually for better simplification --- crates/bevy_pbr/src/meshlet/from_mesh.rs | 89 +++++++++++++++++++++--- 1 file changed, 78 insertions(+), 11 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index ba033b8771502..fb17695c77a11 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -14,8 +14,9 @@ use derive_more::derive::{Display, Error}; use half::f16; use itertools::Itertools; use meshopt::{ - build_meshlets, ffi::meshopt_Meshlet, generate_vertex_remap_multi, simplify, Meshlets, - SimplifyOptions, VertexDataAdapter, VertexStream, + build_meshlets, + ffi::{meshopt_Meshlet, meshopt_simplifyWithAttributes}, + generate_vertex_remap_multi, Meshlets, SimplifyOptions, VertexDataAdapter, VertexStream, }; use metis::Graph; use smallvec::SmallVec; @@ -101,6 +102,8 @@ impl MeshletMesh { Some(&indices), ); + let mut vertex_locks = vec![0; vertices.vertex_count]; + // Build further LODs let mut simplification_queue = 0..meshlets.len(); while simplification_queue.len() > 1 { @@ -119,12 +122,21 @@ impl MeshletMesh { &connected_meshlets_per_meshlet, ); + // Lock borders between groups to prevent cracks when simplifying + lock_group_borders( + &mut vertex_locks, + &groups, + &meshlets, + &position_only_vertex_remap, + position_only_vertex_count as usize, + ); + let next_lod_start = meshlets.len(); for group_meshlets in groups.into_iter().filter(|group| group.len() > 1) { // Simplify the group to ~50% triangle count let Some((simplified_group_indices, mut group_error)) = - simplify_meshlet_group(&group_meshlets, &meshlets, &vertices) + simplify_meshlet_group(&group_meshlets, &meshlets, &vertices, &vertex_locks) else { continue; }; @@ -312,10 +324,48 @@ fn group_meshlets( groups } +fn lock_group_borders( + vertex_locks: &mut [u8], + groups: &[SmallVec<[usize; TARGET_MESHLETS_PER_GROUP]>], + meshlets: &Meshlets, + position_only_vertex_remap: &[u32], + position_only_vertex_count: usize, +) { + let mut position_only_locks = vec![-1; position_only_vertex_count]; + + // Iterate over position-only based vertices of all meshlets in all groups + for (group_id, group_meshlets) in groups.iter().enumerate() { + for meshlet_id in group_meshlets { + let meshlet = meshlets.get(*meshlet_id); + for index in meshlet.triangles { + let vertex_id = + position_only_vertex_remap[meshlet.vertices[*index as usize] as usize] as usize; + + // If the vertex not yet claimed by any group, or was already claimed by this group + if position_only_locks[vertex_id] == -1 + || position_only_locks[vertex_id] == group_id as i32 + { + position_only_locks[vertex_id] = group_id as i32; // Then claim the vertex for this group + } else { + position_only_locks[vertex_id] = -2; // Else vertex was already claimed by another group or was already locked, lock it + } + } + } + } + + // Lock vertices used by more than 1 group + for i in 0..vertex_locks.len() { + let vertex_id = position_only_vertex_remap[i] as usize; + vertex_locks[i] = (position_only_locks[vertex_id] == -2) as u8; + } +} + +#[allow(unsafe_code)] fn simplify_meshlet_group( group_meshlets: &[usize], meshlets: &Meshlets, vertices: &VertexDataAdapter<'_>, + vertex_locks: &[u8], ) -> Option<(Vec, f16)> { // Build a new index buffer into the mesh vertex data by combining all meshlet data in the group let mut group_indices = Vec::new(); @@ -329,14 +379,31 @@ fn simplify_meshlet_group( // Simplify the group to ~50% triangle count // TODO: Simplify using vertex attributes let mut error = 0.0; - let simplified_group_indices = simplify( - &group_indices, - vertices, - group_indices.len() / 2, - f32::MAX, - SimplifyOptions::LockBorder | SimplifyOptions::Sparse | SimplifyOptions::ErrorAbsolute, /* TODO: Specify manual vertex locks instead of meshopt's overly-strict locks */ - Some(&mut error), - ); + let simplified_group_indices = unsafe { + let vertex_data = vertices.reader.get_ref(); + let vertex_data = vertex_data.as_ptr().cast::(); + let positions = vertex_data.add(vertices.position_offset); + let mut result: Vec = vec![0; group_indices.len()]; + let index_count = meshopt_simplifyWithAttributes( + result.as_mut_ptr().cast(), + group_indices.as_ptr().cast(), + group_indices.len(), + positions.cast::(), + vertices.vertex_count, + vertices.vertex_stride, + std::ptr::null(), + 0, + std::ptr::null(), + 0, + vertex_locks.as_ptr().cast(), + group_indices.len() / 2, + f32::MAX, + (SimplifyOptions::Sparse | SimplifyOptions::ErrorAbsolute).bits(), + (&mut error) as *mut _, + ); + result.resize(index_count, 0u32); + result + }; // Check if we were able to simplify at least a little if simplified_group_indices.len() as f32 / group_indices.len() as f32 From bad03cf12f2fedb4e7504cbf21cdbd6479083a80 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Sat, 12 Oct 2024 22:06:49 -0700 Subject: [PATCH 24/29] Small doc fix --- crates/bevy_pbr/src/meshlet/from_mesh.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index fb17695c77a11..094c809a5b0cb 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -23,7 +23,7 @@ use smallvec::SmallVec; // Aim to have 8 meshlets per group const TARGET_MESHLETS_PER_GROUP: usize = 8; -// Reject groups that keep at least 95% of their original triangles +// Reject groups that keep over 95% of their original triangles const SIMPLIFICATION_FAILURE_PERCENTAGE: f32 = 0.95; /// Default vertex position quantization factor for use with [`MeshletMesh::from_mesh`]. From 6eda2aa2d3af2f5aea2dc9c55f5321dda12a8ab5 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Sun, 13 Oct 2024 09:27:10 -0700 Subject: [PATCH 25/29] Comment missing a word --- crates/bevy_pbr/src/meshlet/from_mesh.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index 094c809a5b0cb..464c60e32a18a 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -341,7 +341,7 @@ fn lock_group_borders( let vertex_id = position_only_vertex_remap[meshlet.vertices[*index as usize] as usize] as usize; - // If the vertex not yet claimed by any group, or was already claimed by this group + // If the vertex is not yet claimed by any group, or was already claimed by this group if position_only_locks[vertex_id] == -1 || position_only_locks[vertex_id] == group_id as i32 { From aff12ab65e579557df61379eb582d39bce813133 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Sun, 13 Oct 2024 11:03:08 -0700 Subject: [PATCH 26/29] Retry stuck meshlets when building the DAG --- crates/bevy_pbr/src/meshlet/from_mesh.rs | 71 ++++++++++++++---------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index 464c60e32a18a..455b9fc7adf6e 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -9,7 +9,7 @@ use bevy_render::{ }; use bevy_utils::HashMap; use bitvec::{order::Lsb0, vec::BitVec, view::BitView}; -use core::{iter, ops::Range}; +use core::iter; use derive_more::derive::{Display, Error}; use half::f16; use itertools::Itertools; @@ -105,11 +105,12 @@ impl MeshletMesh { let mut vertex_locks = vec![0; vertices.vertex_count]; // Build further LODs - let mut simplification_queue = 0..meshlets.len(); + let mut simplification_queue = Vec::from_iter(0..meshlets.len()); + let mut retry_queue = Vec::new(); while simplification_queue.len() > 1 { // For each meshlet build a list of connected meshlets (meshlets that share a vertex) let connected_meshlets_per_meshlet = find_connected_meshlets( - simplification_queue.clone(), + &simplification_queue, &meshlets, &position_only_vertex_remap, position_only_vertex_count as usize, @@ -117,10 +118,7 @@ impl MeshletMesh { // Group meshlets into roughly groups of size TARGET_MESHLETS_PER_GROUP, // grouping meshlets with a high number of shared vertices - let groups = group_meshlets( - simplification_queue.clone(), - &connected_meshlets_per_meshlet, - ); + let groups = group_meshlets(&connected_meshlets_per_meshlet, &simplification_queue); // Lock borders between groups to prevent cracks when simplifying lock_group_borders( @@ -132,12 +130,19 @@ impl MeshletMesh { ); let next_lod_start = meshlets.len(); + for group_meshlets in groups.into_iter() { + // If the group only has a single meshlet, we can't simplify it well, so retry later + if group_meshlets.len() == 1 { + retry_queue.push(group_meshlets[0]); + continue; + } - for group_meshlets in groups.into_iter().filter(|group| group.len() > 1) { // Simplify the group to ~50% triangle count let Some((simplified_group_indices, mut group_error)) = simplify_meshlet_group(&group_meshlets, &meshlets, &vertices, &vertex_locks) else { + // Couldn't simplify the group enough, retry its meshlets later + retry_queue.extend_from_slice(&group_meshlets); continue; }; @@ -177,7 +182,12 @@ impl MeshletMesh { ); } - simplification_queue = next_lod_start..meshlets.len(); + // Set simplification queue to the list of newly created (and retrying) meshlets + simplification_queue.clear(); + simplification_queue.extend(next_lod_start..meshlets.len()); + if !simplification_queue.is_empty() { + simplification_queue.extend(retry_queue.drain(..)); + } } // Copy vertex attributes per meshlet and compress @@ -237,22 +247,22 @@ fn compute_meshlets(indices: &[u32], vertices: &VertexDataAdapter) -> Meshlets { } fn find_connected_meshlets( - simplification_queue: Range, + simplification_queue: &[usize], meshlets: &Meshlets, position_only_vertex_remap: &[u32], position_only_vertex_count: usize, ) -> Vec> { // For each vertex, build a list of all meshlets that use it let mut vertices_to_meshlets = vec![Vec::new(); position_only_vertex_count]; - for meshlet_id in simplification_queue.clone() { - let meshlet = meshlets.get(meshlet_id); + for (meshlet_queue_id, meshlet_id) in simplification_queue.iter().enumerate() { + let meshlet = meshlets.get(*meshlet_id); for index in meshlet.triangles { let vertex_id = position_only_vertex_remap[meshlet.vertices[*index as usize] as usize]; let vertex_to_meshlets = &mut vertices_to_meshlets[vertex_id as usize]; // Meshlets are added in order, so we can just check the last element to deduplicate, // in the case of two triangles sharing the same vertex within a single meshlet - if vertex_to_meshlets.last() != Some(&meshlet_id) { - vertex_to_meshlets.push(meshlet_id); + if vertex_to_meshlets.last() != Some(&meshlet_queue_id) { + vertex_to_meshlets.push(meshlet_queue_id); } } } @@ -260,9 +270,14 @@ fn find_connected_meshlets( // For each meshlet pair, count how many vertices they share let mut meshlet_pair_to_shared_vertex_count = HashMap::new(); for vertex_meshlet_ids in vertices_to_meshlets { - for (meshlet_id1, meshlet_id2) in vertex_meshlet_ids.into_iter().tuple_combinations() { + for (meshlet_queue_id1, meshlet_queue_id2) in + vertex_meshlet_ids.into_iter().tuple_combinations() + { let count = meshlet_pair_to_shared_vertex_count - .entry((meshlet_id1.min(meshlet_id2), meshlet_id1.max(meshlet_id2))) + .entry(( + meshlet_queue_id1.min(meshlet_queue_id2), + meshlet_queue_id1.max(meshlet_queue_id2), + )) .or_insert(0); *count += 1; } @@ -270,12 +285,12 @@ fn find_connected_meshlets( // For each meshlet, gather all other meshlets that share at least one vertex along with their shared vertex count let mut connected_meshlets = vec![Vec::new(); simplification_queue.len()]; - for ((meshlet_id1, meshlet_id2), shared_count) in meshlet_pair_to_shared_vertex_count { + for ((meshlet_queue_id1, meshlet_queue_id2), shared_count) in + meshlet_pair_to_shared_vertex_count + { // We record both id1->id2 and id2->id1 as adjacency is symmetrical - connected_meshlets[meshlet_id1 - simplification_queue.start] - .push((meshlet_id2, shared_count)); - connected_meshlets[meshlet_id2 - simplification_queue.start] - .push((meshlet_id1, shared_count)); + connected_meshlets[meshlet_queue_id1].push((meshlet_queue_id2, shared_count)); + connected_meshlets[meshlet_queue_id2].push((meshlet_queue_id1, shared_count)); } // The order of meshlets depends on hash traversal order; to produce deterministic results, sort them @@ -288,18 +303,18 @@ fn find_connected_meshlets( // METIS manual: http://glaros.dtc.umn.edu/gkhome/fetch/sw/metis/manual.pdf fn group_meshlets( - simplification_queue: Range, connected_meshlets_per_meshlet: &[Vec<(usize, usize)>], + simplification_queue: &[usize], ) -> Vec> { let mut xadj = Vec::with_capacity(simplification_queue.len() + 1); let mut adjncy = Vec::new(); let mut adjwgt = Vec::new(); - for meshlet_id in simplification_queue.clone() { + for meshlet_queue_id in 0..simplification_queue.len() { xadj.push(adjncy.len() as i32); - for (connected_meshlet_id, shared_vertex_count) in - connected_meshlets_per_meshlet[meshlet_id - simplification_queue.start].iter() + for (connected_meshlet_queue_id, shared_vertex_count) in + connected_meshlets_per_meshlet[meshlet_queue_id].iter() { - adjncy.push((connected_meshlet_id - simplification_queue.start) as i32); + adjncy.push(*connected_meshlet_queue_id as i32); adjwgt.push(*shared_vertex_count as i32); // TODO: Additional weight based on meshlet spatial proximity } @@ -318,8 +333,8 @@ fn group_meshlets( .unwrap(); let mut groups = vec![SmallVec::new(); partition_count]; - for (i, meshlet_group) in group_per_meshlet.into_iter().enumerate() { - groups[meshlet_group as usize].push(i + simplification_queue.start); + for (meshlet_queue_id, meshlet_group) in group_per_meshlet.into_iter().enumerate() { + groups[meshlet_group as usize].push(simplification_queue[meshlet_queue_id]); } groups } From d65879c3cf899955c643894b1b1bd2c907c5228a Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Sun, 13 Oct 2024 11:32:17 -0700 Subject: [PATCH 27/29] Update bunny asset url --- Cargo.toml | 2 +- examples/3d/meshlet.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f92367630c188..d07b61f82bb4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1209,7 +1209,7 @@ setup = [ "curl", "-o", "assets/models/bunny.meshlet_mesh", - "https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/167cdaf0b08f89fb747b83b94c27755f116cd408/bunny.meshlet_mesh", + "https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/8483db58832542383820c3f44e4730e566910be7/bunny.meshlet_mesh", ], ] diff --git a/examples/3d/meshlet.rs b/examples/3d/meshlet.rs index 7f425b40eb8ae..c3aeb76480764 100644 --- a/examples/3d/meshlet.rs +++ b/examples/3d/meshlet.rs @@ -17,7 +17,7 @@ use camera_controller::{CameraController, CameraControllerPlugin}; use std::{f32::consts::PI, path::Path, process::ExitCode}; const ASSET_URL: &str = - "https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/167cdaf0b08f89fb747b83b94c27755f116cd408/bunny.meshlet_mesh"; + "https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/8483db58832542383820c3f44e4730e566910be7/bunny.meshlet_mesh"; fn main() -> ExitCode { if !Path::new("./assets/models/bunny.meshlet_mesh").exists() { From 1ca60e7f4a1b021b2aa3a97663ff18ecbfeaed5d Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Sun, 13 Oct 2024 11:53:10 -0700 Subject: [PATCH 28/29] CI lints --- crates/bevy_pbr/src/meshlet/from_mesh.rs | 29 ++++++++++++------------ 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index 455b9fc7adf6e..aa8be077d25cf 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -113,7 +113,7 @@ impl MeshletMesh { &simplification_queue, &meshlets, &position_only_vertex_remap, - position_only_vertex_count as usize, + position_only_vertex_count, ); // Group meshlets into roughly groups of size TARGET_MESHLETS_PER_GROUP, @@ -126,7 +126,7 @@ impl MeshletMesh { &groups, &meshlets, &position_only_vertex_remap, - position_only_vertex_count as usize, + position_only_vertex_count, ); let next_lod_start = meshlets.len(); @@ -186,7 +186,7 @@ impl MeshletMesh { simplification_queue.clear(); simplification_queue.extend(next_lod_start..meshlets.len()); if !simplification_queue.is_empty() { - simplification_queue.extend(retry_queue.drain(..)); + simplification_queue.append(&mut retry_queue); } } @@ -284,21 +284,21 @@ fn find_connected_meshlets( } // For each meshlet, gather all other meshlets that share at least one vertex along with their shared vertex count - let mut connected_meshlets = vec![Vec::new(); simplification_queue.len()]; + let mut connected_meshlets_per_meshlet = vec![Vec::new(); simplification_queue.len()]; for ((meshlet_queue_id1, meshlet_queue_id2), shared_count) in meshlet_pair_to_shared_vertex_count { // We record both id1->id2 and id2->id1 as adjacency is symmetrical - connected_meshlets[meshlet_queue_id1].push((meshlet_queue_id2, shared_count)); - connected_meshlets[meshlet_queue_id2].push((meshlet_queue_id1, shared_count)); + connected_meshlets_per_meshlet[meshlet_queue_id1].push((meshlet_queue_id2, shared_count)); + connected_meshlets_per_meshlet[meshlet_queue_id2].push((meshlet_queue_id1, shared_count)); } // The order of meshlets depends on hash traversal order; to produce deterministic results, sort them - for list in connected_meshlets.iter_mut() { + for list in connected_meshlets_per_meshlet.iter_mut() { list.sort_unstable(); } - connected_meshlets + connected_meshlets_per_meshlet } // METIS manual: http://glaros.dtc.umn.edu/gkhome/fetch/sw/metis/manual.pdf @@ -309,11 +309,9 @@ fn group_meshlets( let mut xadj = Vec::with_capacity(simplification_queue.len() + 1); let mut adjncy = Vec::new(); let mut adjwgt = Vec::new(); - for meshlet_queue_id in 0..simplification_queue.len() { + for connected_meshlets in connected_meshlets_per_meshlet { xadj.push(adjncy.len() as i32); - for (connected_meshlet_queue_id, shared_vertex_count) in - connected_meshlets_per_meshlet[meshlet_queue_id].iter() - { + for (connected_meshlet_queue_id, shared_vertex_count) in connected_meshlets { adjncy.push(*connected_meshlet_queue_id as i32); adjwgt.push(*shared_vertex_count as i32); // TODO: Additional weight based on meshlet spatial proximity @@ -376,6 +374,7 @@ fn lock_group_borders( } #[allow(unsafe_code)] +#[allow(clippy::undocumented_unsafe_blocks)] fn simplify_meshlet_group( group_meshlets: &[usize], meshlets: &Meshlets, @@ -406,15 +405,15 @@ fn simplify_meshlet_group( positions.cast::(), vertices.vertex_count, vertices.vertex_stride, - std::ptr::null(), + core::ptr::null(), 0, - std::ptr::null(), + core::ptr::null(), 0, vertex_locks.as_ptr().cast(), group_indices.len() / 2, f32::MAX, (SimplifyOptions::Sparse | SimplifyOptions::ErrorAbsolute).bits(), - (&mut error) as *mut _, + core::ptr::from_mut(&mut error), ); result.resize(index_count, 0u32); result From 02b0adac48d04b47d785bd1b43e1eb6c2299254d Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:32:23 -0700 Subject: [PATCH 29/29] Use a non-broken metis manual link --- crates/bevy_pbr/src/meshlet/from_mesh.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index 84027d74b1ceb..d644a64cdbc8a 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -301,7 +301,7 @@ fn find_connected_meshlets( connected_meshlets_per_meshlet } -// METIS manual: http://glaros.dtc.umn.edu/gkhome/fetch/sw/metis/manual.pdf +// METIS manual: https://github.com/KarypisLab/METIS/blob/e0f1b88b8efcb24ffa0ec55eabb78fbe61e58ae7/manual/manual.pdf fn group_meshlets( connected_meshlets_per_meshlet: &[Vec<(usize, usize)>], simplification_queue: &[usize],