Skip to content

Commit

Permalink
Remove meshlet builder retry queue (bevyengine#16941)
Browse files Browse the repository at this point in the history
Revert the retry queue for stuck meshlet groups that couldn't simplify
added in bevyengine#15886.

It was a hack that didn't really work, that was intended to help solve
meshlets getting stuck and never getting simplified further. The actual
solution is a new DAG building algorithm that I have coming in a
followup PR. With that PR, there will be no need for the retry queue, as
meshlets will rarely ever get stuck (I checked, the code never gets
called). I split this off into it's own PR for easier reviewing.

Meshlet IDs during building are back to being relative to the overall
list of meshlets across all LODs, instead of starting at 0 for the first
meshlet in the simplification queue for the current LOD, regardless of
how many meshlets there are in the asset total.

Not going to bother to regenerate the bunny asset for this PR.
  • Loading branch information
JMS55 authored and pcwalton committed Dec 25, 2024
1 parent 24623d9 commit 974c36d
Showing 1 changed file with 28 additions and 37 deletions.
65 changes: 28 additions & 37 deletions crates/bevy_pbr/src/meshlet/from_mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use bevy_render::{
};
use bevy_utils::HashMap;
use bitvec::{order::Lsb0, vec::BitVec, view::BitView};
use core::iter;
use core::{iter, ops::Range};
use half::f16;
use itertools::Itertools;
use meshopt::{
Expand Down Expand Up @@ -105,20 +105,22 @@ impl MeshletMesh {
let mut vertex_locks = vec![false; vertices.vertex_count];

// Build further LODs
let mut simplification_queue = Vec::from_iter(0..meshlets.len());
let mut retry_queue = Vec::new();
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 vertex)
let connected_meshlets_per_meshlet = find_connected_meshlets(
&simplification_queue,
simplification_queue.clone(),
&meshlets,
&position_only_vertex_remap,
position_only_vertex_count,
);

// Group meshlets into roughly groups of size TARGET_MESHLETS_PER_GROUP,
// grouping meshlets with a high number of shared vertices
let groups = group_meshlets(&connected_meshlets_per_meshlet, &simplification_queue);
let groups = group_meshlets(
&connected_meshlets_per_meshlet,
simplification_queue.clone(),
);

// Lock borders between groups to prevent cracks when simplifying
lock_group_borders(
Expand All @@ -131,9 +133,8 @@ 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 the group only has a single meshlet we can't simplify it
if group_meshlets.len() == 1 {
retry_queue.push(group_meshlets[0]);
continue;
}

Expand All @@ -146,8 +147,7 @@ impl MeshletMesh {
vertex_stride,
&vertex_locks,
) else {
// Couldn't simplify the group enough, retry its meshlets later
retry_queue.extend_from_slice(&group_meshlets);
// Couldn't simplify the group enough
continue;
};

Expand Down Expand Up @@ -187,12 +187,8 @@ impl MeshletMesh {
);
}

// 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.append(&mut retry_queue);
}
// Set simplification queue to the list of newly created meshlets
simplification_queue = next_lod_start..meshlets.len();
}

// Copy vertex attributes per meshlet and compress
Expand Down Expand Up @@ -252,50 +248,45 @@ fn compute_meshlets(indices: &[u32], vertices: &VertexDataAdapter) -> Meshlets {
}

fn find_connected_meshlets(
simplification_queue: &[usize],
simplification_queue: Range<usize>,
meshlets: &Meshlets,
position_only_vertex_remap: &[u32],
position_only_vertex_count: usize,
) -> Vec<Vec<(usize, usize)>> {
// 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_queue_id, meshlet_id) in simplification_queue.iter().enumerate() {
let meshlet = meshlets.get(*meshlet_id);
for meshlet_id in simplification_queue.clone() {
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_queue_id) {
vertex_to_meshlets.push(meshlet_queue_id);
if vertex_to_meshlets.last() != Some(&meshlet_id) {
vertex_to_meshlets.push(meshlet_id);
}
}
}

// For each meshlet pair, count how many vertices they share
let mut meshlet_pair_to_shared_vertex_count = <HashMap<_, _>>::default();
for vertex_meshlet_ids in vertices_to_meshlets {
for (meshlet_queue_id1, meshlet_queue_id2) in
vertex_meshlet_ids.into_iter().tuple_combinations()
{
for (meshlet_id1, meshlet_id2) in vertex_meshlet_ids.into_iter().tuple_combinations() {
let count = meshlet_pair_to_shared_vertex_count
.entry((
meshlet_queue_id1.min(meshlet_queue_id2),
meshlet_queue_id1.max(meshlet_queue_id2),
))
.entry((meshlet_id1.min(meshlet_id2), meshlet_id1.max(meshlet_id2)))
.or_insert(0);
*count += 1;
}
}

// For each meshlet, gather all other meshlets that share at least one vertex along with their shared vertex count
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
{
for ((meshlet_id1, meshlet_id2), shared_vertex_count) in meshlet_pair_to_shared_vertex_count {
// We record both id1->id2 and id2->id1 as adjacency is symmetrical
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));
connected_meshlets_per_meshlet[meshlet_id1 - simplification_queue.start]
.push((meshlet_id2, shared_vertex_count));
connected_meshlets_per_meshlet[meshlet_id2 - simplification_queue.start]
.push((meshlet_id1, shared_vertex_count));
}

// The order of meshlets depends on hash traversal order; to produce deterministic results, sort them
Expand All @@ -309,15 +300,15 @@ fn find_connected_meshlets(
// 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],
simplification_queue: Range<usize>,
) -> Vec<SmallVec<[usize; TARGET_MESHLETS_PER_GROUP]>> {
let mut xadj = Vec::with_capacity(simplification_queue.len() + 1);
let mut adjncy = Vec::new();
let mut adjwgt = Vec::new();
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 {
adjncy.push(*connected_meshlet_queue_id as i32);
for (connected_meshlet_id, shared_vertex_count) in connected_meshlets {
adjncy.push((connected_meshlet_id - simplification_queue.start) as i32);
adjwgt.push(*shared_vertex_count as i32);
// TODO: Additional weight based on meshlet spatial proximity
}
Expand All @@ -336,8 +327,8 @@ fn group_meshlets(
.unwrap();

let mut groups = vec![SmallVec::new(); partition_count];
for (meshlet_queue_id, meshlet_group) in group_per_meshlet.into_iter().enumerate() {
groups[meshlet_group as usize].push(simplification_queue[meshlet_queue_id]);
for (i, meshlet_group) in group_per_meshlet.into_iter().enumerate() {
groups[meshlet_group as usize].push(i + simplification_queue.start);
}
groups
}
Expand Down

0 comments on commit 974c36d

Please sign in to comment.