perf: eliminate initial hover freeze

This commit is contained in:
Hakan Seven committed 2026-07-25 10:09:23 +03:00
1 parent 47423eae18
commit 2bc4c1c8f3
7 files changed
+554 -126

No files matched your search

+21 -6
View File
@@ -671,6 +671,24 @@ pub fn mesh_click_hit<'a>(
let v = &mesh.verts;
let idx = &mesh.indices;
let lo = &mesh.verts_low;
// Indexed meshes reuse most vertices across several triangles.
// Project once per vertex instead of three matrix transforms per
// triangle; dense solid misses are otherwise the worst-case hover.
let projected: Vec<(Point, f32)> = v
.iter()
.enumerate()
.map(|(i, &vertex)| {
let ndc =
view_rot.project_point3((mesh_vert(vertex, lo, i) - eye).as_vec3());
(
Point::new(
(ndc.x + 1.0) * 0.5 * bounds.width,
(1.0 - ndc.y) * 0.5 * bounds.height,
),
ndc.z,
)
})
.collect();
let mut t = 0;
while t + 2 < idx.len() {
let tri = [idx[t] as usize, idx[t + 1] as usize, idx[t + 2] as usize];
@@ -678,12 +696,9 @@ pub fn mesh_click_hit<'a>(
let mut sp = [Point::ORIGIN; 3];
let mut depth = 0.0f32;
for (j, &k) in tri.iter().enumerate() {
let ndc = view_rot.project_point3((mesh_vert(v[k], lo, k) - eye).as_vec3());
sp[j] = Point::new(
(ndc.x + 1.0) * 0.5 * bounds.width,
(1.0 - ndc.y) * 0.5 * bounds.height,
);
depth += ndc.z;
let (point, z) = projected[k];
sp[j] = point;
depth += z;
}
if point_in_polygon(cursor, &sp) {
let d = depth / 3.0;
+90 -4
View File
@@ -140,12 +140,12 @@ fn build_bvh2_node<T>(
return node_idx;
}
let axis = usize::from(bounds[3] - bounds[1] > bounds[2] - bounds[0]);
order.sort_unstable_by(|&a, &b| {
let mid = order.len() / 2;
order.select_nth_unstable_by(mid, |&a, &b| {
let aa = entry_aabb2(&entries[a as usize]);
let bb = entry_aabb2(&entries[b as usize]);
(aa[axis] + aa[axis + 2]).total_cmp(&(bb[axis] + bb[axis + 2]))
});
let mid = order.len() / 2;
let (left_order, right_order) = order.split_at_mut(mid);
let left = build_bvh2_node(entries, left_order, base, nodes);
let right = build_bvh2_node(entries, right_order, base + mid, nodes);
@@ -253,12 +253,12 @@ fn build_bvh3_node<T>(
axis = candidate;
}
}
order.sort_unstable_by(|&a, &b| {
let mid = order.len() / 2;
order.select_nth_unstable_by(mid, |&a, &b| {
let aa = entries[a as usize].aabb;
let bb = entries[b as usize].aabb;
(aa[axis] + aa[axis + 3]).total_cmp(&(bb[axis] + bb[axis + 3]))
});
let mid = order.len() / 2;
let (left_order, right_order) = order.split_at_mut(mid);
let left = build_bvh3_node(entries, left_order, base, nodes);
let right = build_bvh3_node(entries, right_order, base + mid, nodes);
@@ -520,6 +520,11 @@ impl<T: Copy + Ord + Sync> SpatialSet<T> {
self.xy.query(&self.entries, aabb)
}
fn prepare_screen(&self) {
self.xyz
.get_or_init(|| SpatialBvh3::build(&self.entries));
}
fn query_screen(
&self,
screen_rect: [f32; 4],
@@ -951,6 +956,52 @@ impl InteractionIndex {
}
}
/// Build every projected 3D broad phase before the index reaches the UI
/// thread. Perspective/orbit hover must never pay this one-time cost.
pub fn prepare_screen(&self) {
#[cfg(not(target_arch = "wasm32"))]
rayon::join(
|| {
rayon::join(
|| rayon::join(|| self.wires.prepare_screen(), || self.segments.prepare_screen()),
|| {
rayon::join(
|| self.snap_points.prepare_screen(),
|| self.key_vertices.prepare_screen(),
)
},
)
},
|| {
rayon::join(
|| {
rayon::join(
|| self.key_segments.prepare_screen(),
|| self.fill_triangles.prepare_screen(),
)
},
|| {
rayon::join(
|| self.pick_triangles.prepare_screen(),
|| self.glyphs.prepare_screen(),
)
},
)
},
);
#[cfg(target_arch = "wasm32")]
{
self.wires.prepare_screen();
self.segments.prepare_screen();
self.snap_points.prepare_screen();
self.key_vertices.prepare_screen();
self.key_segments.prepare_screen();
self.fill_triangles.prepare_screen();
self.pick_triangles.prepare_screen();
self.glyphs.prepare_screen();
}
}
pub fn pick_radius_px(&self, base_radius_px: f32) -> f32 {
base_radius_px.max(self.max_line_half_width_px)
}
@@ -1241,6 +1292,17 @@ impl InteractionIndex {
}
}
impl std::fmt::Debug for InteractionIndex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InteractionIndex")
.field("wires", &self.wire_handles.len())
.field("segments", &self.segments.entries.len())
.field("fill_triangles", &self.fill_triangles.entries.len())
.field("pick_triangles", &self.pick_triangles.entries.len())
.finish()
}
}
pub struct InteractionCandidates {
wires: Arc<Vec<WireModel>>,
wire_indices: Option<Vec<u32>>,
@@ -1302,6 +1364,30 @@ impl InteractionCandidates {
}
}
/// Empty but explicitly indexed candidate set. Used while a large source
/// is still being prepared off-thread so UI input never falls back to a
/// full-scene scan.
pub fn pending(wires: Arc<Vec<WireModel>>) -> Self {
Self {
wires,
wire_indices: Some(Vec::new()),
segments: Some(Vec::new()),
snap_points: Some(Vec::new()),
key_vertices: Some(Vec::new()),
key_segments: Some(Vec::new()),
fill_triangles: Some(Vec::new()),
pick_triangles: Some(Vec::new()),
glyphs: Some(Vec::new()),
query_aabb: None,
screen_rect: None,
screen_view: None,
}
}
pub fn is_indexed(&self) -> bool {
self.wire_indices.is_some()
}
pub fn iter(&self) -> WireIter<'_> {
match &self.wire_indices {
Some(indices) => WireIter::Indexed {