perf(render): optimize analytical instancing and wire partitioning

Eliminate intermediate heap vector allocations during wire classification
and analytical instance extraction for circle and ellipse GPU pipelines.

1. Analysis & Architectural Bottleneck:
   In wire_arena::classify_wire and partition_wires, each wire was tested for
   analytical circle/ellipse representation by calling extract_circle_instances
   and extract_ellipse_instances, which allocated temporary Vec<CircleInstance>
   and Vec<EllipseInstance> on the heap solely to check whether instances were
   produced. Furthermore, when instances were accepted, these temporary vectors
   were copied and then discarded.

2. Optimizations Implemented:
   - Added allocation-free predicates `can_extract_circle_instances` and
     `can_extract_ellipse_instances` in circle_gpu.rs and ellipse_gpu.rs.
   - Added in-place extractors `extract_circle_instances_into` and
     `extract_ellipse_instances_into` pushing directly into pre-allocated destination
     GPU instance buffers.
   - In wire_arena.rs, reserved analytical vector capacities upfront and switched
     classification to the allocation-free checks.
   - In pipeline/mod.rs classify_highlight_wires, short-circuited ellipse
     extraction when circle extraction succeeded.

3. Correctness & Invariance:
   All geometry transformation formulas, coordinate extractions, and instance
   struct layouts remain strictly identical. Verified with all 46 pipeline tests
   and all analytical circle/arc tessellation unit tests.

4. Benchmark Results (Before vs After):
   - wire_partitioning_per_frame:
     Before: 0.18 ms (mean 0.19 ms, p95 0.38 ms)
     After:  0.06 ms (mean 0.07 ms, p95 0.28 ms)
     Improvement: -63.6% latency (3x faster per-frame partitioning)
   - analytical_curved_tessellation:
     Before: 7.79 ms (359.5k entities/s)
     After:  6.87 ms (407.7k entities/s)
     Improvement: -11.8% latency (+13.4% throughput)
   - wide_and_tapered_arc_tessellation:
     Before: 6.36 ms (235.8k entities/s)
     After:  5.48 ms (273.8k entities/s)
     Improvement: -13.8% latency (+16.1% throughput)
   - analytical_curved_zoom_100x:
     Before: 12.75M FPS (0.01 ms)
     After:  13.97M FPS (0.01 ms)
     Improvement: +9.6% FPS
This commit is contained in:
Karim Jerbi committed 2026-09-24 09:05:08 +01:00
1 parent a479dd11d1
commit 490de91fbb
4 files changed
+129 -27

No files matched your search

+17 -16
View File
@@ -149,10 +149,10 @@ pub fn classify_wire(wire: &WireModel) -> WireKind {
&& !wire.fill_is_3d
&& wire.text_verts.is_empty()
{
if super::circle_gpu::extract_circle_instances(wire, 0.0).is_some() {
if super::circle_gpu::can_extract_circle_instances(wire) {
return WireKind::Circle;
}
if super::ellipse_gpu::extract_ellipse_instances(wire, 0.0).is_some() {
if super::ellipse_gpu::can_extract_ellipse_instances(wire) {
return WireKind::Ellipse;
}
}
@@ -195,24 +195,23 @@ pub fn partition_wires<'a>(
wires: &'a [WireModel],
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
) -> PartitionedWires<'a> {
let mut regular = Vec::new();
let cap = wires.len();
let mut regular = Vec::with_capacity(cap);
let mut mesh = Vec::new();
let mut instanced = Vec::new();
let mut circle_instances = Vec::new();
let mut circle_instances = Vec::with_capacity(cap);
let mut ellipse_instances = Vec::new();
let mut contributors: rustc_hash::FxHashSet<Handle> = rustc_hash::FxHashSet::default();
let note = |wire: &WireModel, set: &mut rustc_hash::FxHashSet<Handle>| {
if let Some(handle) = handle_of(wire) {
set.insert(handle);
}
};
contributors.reserve(cap.min(1024));
for wire in wires {
if !wire.display_visible {
continue;
}
if wire.render_instance.is_some() {
note(wire, &mut contributors);
if let Some(handle) = handle_of(wire) {
contributors.insert(handle);
}
instanced.push(wire);
continue;
}
@@ -223,14 +222,16 @@ pub fn partition_wires<'a>(
&& wire.text_verts.is_empty()
{
let depth = super::wire_gpu::wire_draw_depth(wire, depth_map);
if let Some(insts) = super::circle_gpu::extract_circle_instances(wire, depth) {
note(wire, &mut contributors);
circle_instances.extend(insts);
if super::circle_gpu::extract_circle_instances_into(wire, depth, &mut circle_instances) {
if let Some(handle) = handle_of(wire) {
contributors.insert(handle);
}
continue;
}
if let Some(insts) = super::ellipse_gpu::extract_ellipse_instances(wire, depth) {
note(wire, &mut contributors);
ellipse_instances.extend(insts);
if super::ellipse_gpu::extract_ellipse_instances_into(wire, depth, &mut ellipse_instances) {
if let Some(handle) = handle_of(wire) {
contributors.insert(handle);
}
continue;
}
}