Skip to content

Commit 4f0b0e0

Browse files
zeuxmockersf
authored andcommitted
Make meshlet processing deterministic (#13913)
This is a followup to #13904 based on the discussion there, and switches two HashMaps that used meshlet ids as keys to Vec. In addition to a small further performance boost for `from_mesh` (1.66s => 1.60s), this makes processing deterministic modulo threading issues wrt CRT rand described in the linked PR. This is valuable for debugging, as you can visually or programmatically inspect the meshlet distribution before/after making changes that should not change the output, whereas previously every asset rebuild would change the meshlet structure. Tested with #13431; after this change, the visual output of meshlets is consistent between asset rebuilds, and the MD5 of the output GLB file does not change either, which was not the case before.
1 parent cc17647 commit 4f0b0e0

File tree

1 file changed

+18
-28
lines changed

1 file changed

+18
-28
lines changed

crates/bevy_pbr/src/meshlet/from_mesh.rs

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,10 @@ impl MeshletMesh {
7474

7575
let next_lod_start = meshlets.len();
7676

77-
for group_meshlets in groups.values().filter(|group| group.len() > 1) {
77+
for group_meshlets in groups.into_iter().filter(|group| group.len() > 1) {
7878
// Simplify the group to ~50% triangle count
7979
let Some((simplified_group_indices, mut group_error)) = simplify_meshlet_groups(
80-
group_meshlets,
80+
&group_meshlets,
8181
&meshlets,
8282
&vertices,
8383
lod_level,
@@ -101,7 +101,7 @@ impl MeshletMesh {
101101

102102
// For each meshlet in the group set their parent LOD bounding sphere to that of the simplified group
103103
for meshlet_id in group_meshlets {
104-
bounding_spheres[*meshlet_id].parent_lod = group_bounding_sphere;
104+
bounding_spheres[meshlet_id].parent_lod = group_bounding_sphere;
105105
}
106106

107107
// Build new meshlets using the simplified group
@@ -199,7 +199,7 @@ fn compute_meshlets(indices: &[u32], vertices: &VertexDataAdapter) -> Meshlets {
199199
fn find_connected_meshlets(
200200
simplification_queue: Range<usize>,
201201
meshlets: &Meshlets,
202-
) -> HashMap<usize, Vec<(usize, usize)>> {
202+
) -> Vec<Vec<(usize, usize)>> {
203203
// For each edge, gather all meshlets that use it
204204
let mut edges_to_meshlets = HashMap::new();
205205

@@ -236,64 +236,54 @@ fn find_connected_meshlets(
236236
}
237237

238238
// For each meshlet, gather all meshlets that share at least one edge along with shared edge count
239-
let mut connected_meshlets = HashMap::new();
240-
241-
for meshlet_id in simplification_queue.clone() {
242-
connected_meshlets.insert(meshlet_id, Vec::new());
243-
}
239+
let mut connected_meshlets = vec![Vec::new(); simplification_queue.len()];
244240

245241
for ((meshlet_id1, meshlet_id2), shared_count) in shared_edge_count {
246242
// We record id1->id2 and id2->id1 as adjacency is symmetrical
247-
connected_meshlets
248-
.get_mut(&meshlet_id1)
249-
.unwrap()
243+
connected_meshlets[meshlet_id1 - simplification_queue.start]
250244
.push((meshlet_id2, shared_count));
251-
connected_meshlets
252-
.get_mut(&meshlet_id2)
253-
.unwrap()
245+
connected_meshlets[meshlet_id2 - simplification_queue.start]
254246
.push((meshlet_id1, shared_count));
255247
}
256248

257249
// The order of meshlets depends on hash traversal order; to produce deterministic results, sort them
258-
for (_, connected_meshlets) in connected_meshlets.iter_mut() {
259-
connected_meshlets.sort_unstable();
250+
for list in connected_meshlets.iter_mut() {
251+
list.sort_unstable();
260252
}
261253

262254
connected_meshlets
263255
}
264256

265257
fn group_meshlets(
266258
simplification_queue: Range<usize>,
267-
connected_meshlets_per_meshlet: &HashMap<usize, Vec<(usize, usize)>>,
268-
) -> HashMap<i32, Vec<usize>> {
259+
connected_meshlets_per_meshlet: &[Vec<(usize, usize)>],
260+
) -> Vec<Vec<usize>> {
269261
let mut xadj = Vec::with_capacity(simplification_queue.len() + 1);
270262
let mut adjncy = Vec::new();
271263
let mut adjwgt = Vec::new();
272264
for meshlet_id in simplification_queue.clone() {
273265
xadj.push(adjncy.len() as i32);
274266
for (connected_meshlet_id, shared_edge_count) in
275-
connected_meshlets_per_meshlet[&meshlet_id].iter().copied()
267+
connected_meshlets_per_meshlet[meshlet_id - simplification_queue.start].iter()
276268
{
277269
adjncy.push((connected_meshlet_id - simplification_queue.start) as i32);
278-
adjwgt.push(shared_edge_count as i32);
270+
adjwgt.push(*shared_edge_count as i32);
279271
}
280272
}
281273
xadj.push(adjncy.len() as i32);
282274

283275
let mut group_per_meshlet = vec![0; simplification_queue.len()];
284-
let partition_count = (simplification_queue.len().div_ceil(4)) as i32;
285-
Graph::new(1, partition_count, &xadj, &adjncy)
276+
let partition_count = simplification_queue.len().div_ceil(4);
277+
Graph::new(1, partition_count as i32, &xadj, &adjncy)
286278
.unwrap()
287279
.set_adjwgt(&adjwgt)
288280
.part_kway(&mut group_per_meshlet)
289281
.unwrap();
290282

291-
let mut groups = HashMap::new();
283+
let mut groups = vec![Vec::new(); partition_count];
284+
292285
for (i, meshlet_group) in group_per_meshlet.into_iter().enumerate() {
293-
groups
294-
.entry(meshlet_group)
295-
.or_insert(Vec::new())
296-
.push(i + simplification_queue.start);
286+
groups[meshlet_group as usize].push(i + simplification_queue.start);
297287
}
298288
groups
299289
}

0 commit comments

Comments
 (0)