forked from 1b-agent/opencadstudio-upstream
perf: speed large drawing workflows
This commit is contained in:
1 parent
97be4fefaa
commit
39449ec684
10 files changed
+700
-195
No files matched your search
@@ -278,7 +278,7 @@ struct SpatialGrid {
|
||||
}
|
||||
|
||||
impl SpatialGrid {
|
||||
fn build<T>(entries: &[Entry3<T>]) -> Self {
|
||||
fn build<T: Sync>(entries: &[Entry3<T>]) -> Self {
|
||||
if entries.is_empty() {
|
||||
return Self {
|
||||
min: [0.0; 2],
|
||||
@@ -314,57 +314,137 @@ impl SpatialGrid {
|
||||
let cols = (((ext_x / cell).ceil() as u64) + 1).clamp(1, MAX_AXIS_CELLS as u64) as u32;
|
||||
let rows = (((ext_y / cell).ceil() as u64) + 1).clamp(1, MAX_AXIS_CELLS as u64) as u32;
|
||||
let cell_count = cols as usize * rows as usize;
|
||||
let mut counts = vec![0u32; cell_count];
|
||||
let mut oversized = Vec::new();
|
||||
|
||||
let col_of = |x: f64| (((x - min[0]) / cell).floor()).clamp(0.0, (cols - 1) as f64) as u32;
|
||||
let row_of = |y: f64| (((y - min[1]) / cell).floor()).clamp(0.0, (rows - 1) as f64) as u32;
|
||||
|
||||
for (idx, entry) in entries.iter().enumerate() {
|
||||
let aabb = entry_aabb2(entry);
|
||||
let c0 = col_of(aabb[0]);
|
||||
let c1 = col_of(aabb[2]);
|
||||
let r0 = row_of(aabb[1]);
|
||||
let r1 = row_of(aabb[3]);
|
||||
let span = (c1 - c0 + 1) as u64 * (r1 - r0 + 1) as u64;
|
||||
if span > MAX_SPAN_CELLS {
|
||||
oversized.push(idx as u32);
|
||||
continue;
|
||||
}
|
||||
for row in r0..=r1 {
|
||||
let base = row as usize * cols as usize;
|
||||
for col in c0..=c1 {
|
||||
counts[base + col as usize] += 1;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let (counts, oversized) = {
|
||||
use crate::par::prelude::*;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
let counts: Vec<AtomicU32> =
|
||||
(0..cell_count).map(|_| AtomicU32::new(0)).collect();
|
||||
let oversized: Vec<u32> = entries
|
||||
.par_iter()
|
||||
.enumerate()
|
||||
.filter_map(|(idx, entry)| {
|
||||
let aabb = entry_aabb2(entry);
|
||||
let c0 = col_of(aabb[0]);
|
||||
let c1 = col_of(aabb[2]);
|
||||
let r0 = row_of(aabb[1]);
|
||||
let r1 = row_of(aabb[3]);
|
||||
let span = (c1 - c0 + 1) as u64 * (r1 - r0 + 1) as u64;
|
||||
if span > MAX_SPAN_CELLS {
|
||||
return Some(idx as u32);
|
||||
}
|
||||
for row in r0..=r1 {
|
||||
let base = row as usize * cols as usize;
|
||||
for col in c0..=c1 {
|
||||
counts[base + col as usize].fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect();
|
||||
(
|
||||
counts
|
||||
.into_iter()
|
||||
.map(AtomicU32::into_inner)
|
||||
.collect::<Vec<u32>>(),
|
||||
oversized,
|
||||
)
|
||||
};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let (counts, oversized) = {
|
||||
let mut counts = vec![0u32; cell_count];
|
||||
let mut oversized = Vec::new();
|
||||
for (idx, entry) in entries.iter().enumerate() {
|
||||
let aabb = entry_aabb2(entry);
|
||||
let c0 = col_of(aabb[0]);
|
||||
let c1 = col_of(aabb[2]);
|
||||
let r0 = row_of(aabb[1]);
|
||||
let r1 = row_of(aabb[3]);
|
||||
let span = (c1 - c0 + 1) as u64 * (r1 - r0 + 1) as u64;
|
||||
if span > MAX_SPAN_CELLS {
|
||||
oversized.push(idx as u32);
|
||||
continue;
|
||||
}
|
||||
for row in r0..=r1 {
|
||||
let base = row as usize * cols as usize;
|
||||
for col in c0..=c1 {
|
||||
counts[base + col as usize] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(counts, oversized)
|
||||
};
|
||||
let mut cell_offsets = Vec::with_capacity(cell_count + 1);
|
||||
cell_offsets.push(0);
|
||||
for count in counts {
|
||||
cell_offsets.push(cell_offsets.last().copied().unwrap_or(0) + count);
|
||||
}
|
||||
let mut cell_entries = vec![0u32; *cell_offsets.last().unwrap_or(&0) as usize];
|
||||
let mut cursors = cell_offsets[..cell_count].to_vec();
|
||||
for (idx, entry) in entries.iter().enumerate() {
|
||||
let aabb = entry_aabb2(entry);
|
||||
let c0 = col_of(aabb[0]);
|
||||
let c1 = col_of(aabb[2]);
|
||||
let r0 = row_of(aabb[1]);
|
||||
let r1 = row_of(aabb[3]);
|
||||
let span = (c1 - c0 + 1) as u64 * (r1 - r0 + 1) as u64;
|
||||
if span > MAX_SPAN_CELLS {
|
||||
continue;
|
||||
}
|
||||
for row in r0..=r1 {
|
||||
let base = row as usize * cols as usize;
|
||||
for col in c0..=c1 {
|
||||
let cell_idx = base + col as usize;
|
||||
let cursor = &mut cursors[cell_idx];
|
||||
cell_entries[*cursor as usize] = idx as u32;
|
||||
*cursor += 1;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let cell_entries = {
|
||||
use crate::par::prelude::*;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
let cursors: Vec<AtomicU32> = cell_offsets[..cell_count]
|
||||
.iter()
|
||||
.copied()
|
||||
.map(AtomicU32::new)
|
||||
.collect();
|
||||
let slots: Vec<AtomicU32> = (0..*cell_offsets.last().unwrap_or(&0) as usize)
|
||||
.map(|_| AtomicU32::new(0))
|
||||
.collect();
|
||||
entries.par_iter().enumerate().for_each(|(idx, entry)| {
|
||||
let aabb = entry_aabb2(entry);
|
||||
let c0 = col_of(aabb[0]);
|
||||
let c1 = col_of(aabb[2]);
|
||||
let r0 = row_of(aabb[1]);
|
||||
let r1 = row_of(aabb[3]);
|
||||
let span = (c1 - c0 + 1) as u64 * (r1 - r0 + 1) as u64;
|
||||
if span > MAX_SPAN_CELLS {
|
||||
return;
|
||||
}
|
||||
for row in r0..=r1 {
|
||||
let base = row as usize * cols as usize;
|
||||
for col in c0..=c1 {
|
||||
let cell_idx = base + col as usize;
|
||||
let cursor = cursors[cell_idx].fetch_add(1, Ordering::Relaxed);
|
||||
slots[cursor as usize].store(idx as u32, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
});
|
||||
slots.into_iter().map(AtomicU32::into_inner).collect()
|
||||
};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let cell_entries = {
|
||||
let mut cell_entries = vec![0u32; *cell_offsets.last().unwrap_or(&0) as usize];
|
||||
let mut cursors = cell_offsets[..cell_count].to_vec();
|
||||
for (idx, entry) in entries.iter().enumerate() {
|
||||
let aabb = entry_aabb2(entry);
|
||||
let c0 = col_of(aabb[0]);
|
||||
let c1 = col_of(aabb[2]);
|
||||
let r0 = row_of(aabb[1]);
|
||||
let r1 = row_of(aabb[3]);
|
||||
let span = (c1 - c0 + 1) as u64 * (r1 - r0 + 1) as u64;
|
||||
if span > MAX_SPAN_CELLS {
|
||||
continue;
|
||||
}
|
||||
for row in r0..=r1 {
|
||||
let base = row as usize * cols as usize;
|
||||
for col in c0..=c1 {
|
||||
let cell_idx = base + col as usize;
|
||||
let cursor = &mut cursors[cell_idx];
|
||||
cell_entries[*cursor as usize] = idx as u32;
|
||||
*cursor += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cell_entries
|
||||
};
|
||||
let oversized = SpatialBvh2::build(&entries, oversized);
|
||||
|
||||
Self {
|
||||
@@ -426,7 +506,7 @@ struct SpatialSet<T> {
|
||||
xyz: std::sync::OnceLock<SpatialBvh3>,
|
||||
}
|
||||
|
||||
impl<T: Copy + Ord> SpatialSet<T> {
|
||||
impl<T: Copy + Ord + Sync> SpatialSet<T> {
|
||||
fn build(entries: Vec<Entry3<T>>) -> Self {
|
||||
let xy = SpatialGrid::build(&entries);
|
||||
Self {
|
||||
@@ -601,6 +681,19 @@ fn collect_wire_index_entries(wire_idx: u32, wire: &WireModel) -> WireIndexEntri
|
||||
entries
|
||||
}
|
||||
|
||||
fn flatten_entry_parts<T>(mut parts: Vec<Vec<T>>) -> Vec<T> {
|
||||
let Some((largest, _)) = parts.iter().enumerate().max_by_key(|(_, part)| part.len()) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut output = parts.swap_remove(largest);
|
||||
let remaining: usize = parts.iter().map(Vec::len).sum();
|
||||
output.reserve(remaining);
|
||||
for mut part in parts {
|
||||
output.append(&mut part);
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
impl InteractionHandleIndex {
|
||||
pub fn build(entries: impl IntoIterator<Item = (u64, [f64; 6])>) -> Self {
|
||||
Self {
|
||||
@@ -649,6 +742,10 @@ impl InteractionIndex {
|
||||
}
|
||||
|
||||
pub fn build(wires: &[WireModel]) -> Self {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let perf = std::env::var_os("OCS_PERF").is_some();
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let build_started = std::time::Instant::now();
|
||||
let wire_handles: Vec<Option<u64>> = wires
|
||||
.iter()
|
||||
.map(|wire| wire.name.parse::<u64>().ok())
|
||||
@@ -665,14 +762,11 @@ impl InteractionIndex {
|
||||
Some(current)
|
||||
})
|
||||
.collect();
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let handles_elapsed = build_started.elapsed();
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let collect_started = std::time::Instant::now();
|
||||
let mut wire_entries = Vec::with_capacity(wires.len());
|
||||
let mut segment_entries = Vec::new();
|
||||
let mut snap_point_entries = Vec::new();
|
||||
let mut key_vertex_entries = Vec::new();
|
||||
let mut key_segment_entries = Vec::new();
|
||||
let mut fill_triangle_entries = Vec::new();
|
||||
let mut pick_triangle_entries = Vec::new();
|
||||
let mut glyph_entries = Vec::new();
|
||||
let mut unbounded_wires = Vec::new();
|
||||
let mut max_line_half_width_px = 0.0f32;
|
||||
|
||||
@@ -691,46 +785,18 @@ impl InteractionIndex {
|
||||
.enumerate()
|
||||
.map(|(index, wire)| collect_wire_index_entries(index as u32, wire))
|
||||
.collect();
|
||||
|
||||
// Every per-wire worker knows its exact output sizes. Reserve the flat
|
||||
// arrays once before draining them so a dense block drawing does not
|
||||
// repeatedly copy already-flattened entries while the Vecs grow.
|
||||
wire_entries.reserve(per_wire.iter().filter(|entries| entries.wire.is_some()).count());
|
||||
unbounded_wires.reserve(per_wire.iter().filter(|entries| entries.unbounded).count());
|
||||
segment_entries.reserve(per_wire.iter().map(|entries| entries.segments.len()).sum());
|
||||
snap_point_entries.reserve(
|
||||
per_wire
|
||||
.iter()
|
||||
.map(|entries| entries.snap_points.len())
|
||||
.sum(),
|
||||
);
|
||||
key_vertex_entries.reserve(
|
||||
per_wire
|
||||
.iter()
|
||||
.map(|entries| entries.key_vertices.len())
|
||||
.sum(),
|
||||
);
|
||||
key_segment_entries.reserve(
|
||||
per_wire
|
||||
.iter()
|
||||
.map(|entries| entries.key_segments.len())
|
||||
.sum(),
|
||||
);
|
||||
fill_triangle_entries.reserve(
|
||||
per_wire
|
||||
.iter()
|
||||
.map(|entries| entries.fill_triangles.len())
|
||||
.sum(),
|
||||
);
|
||||
pick_triangle_entries.reserve(
|
||||
per_wire
|
||||
.iter()
|
||||
.map(|entries| entries.pick_triangles.len())
|
||||
.sum(),
|
||||
);
|
||||
glyph_entries.reserve(per_wire.iter().map(|entries| entries.glyphs.len()).sum());
|
||||
|
||||
for (wire_idx, mut entries) in per_wire.into_iter().enumerate() {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let collect_elapsed = collect_started.elapsed();
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let flatten_started = std::time::Instant::now();
|
||||
let mut segment_parts = Vec::with_capacity(per_wire.len());
|
||||
let mut snap_point_parts = Vec::with_capacity(per_wire.len());
|
||||
let mut key_vertex_parts = Vec::with_capacity(per_wire.len());
|
||||
let mut key_segment_parts = Vec::with_capacity(per_wire.len());
|
||||
let mut fill_triangle_parts = Vec::with_capacity(per_wire.len());
|
||||
let mut pick_triangle_parts = Vec::with_capacity(per_wire.len());
|
||||
let mut glyph_parts = Vec::with_capacity(per_wire.len());
|
||||
for (wire_idx, entries) in per_wire.into_iter().enumerate() {
|
||||
max_line_half_width_px =
|
||||
max_line_half_width_px.max(entries.max_line_half_width_px);
|
||||
if let Some(entry) = entries.wire {
|
||||
@@ -739,14 +805,65 @@ impl InteractionIndex {
|
||||
if entries.unbounded {
|
||||
unbounded_wires.push(wire_idx as u32);
|
||||
}
|
||||
segment_entries.append(&mut entries.segments);
|
||||
snap_point_entries.append(&mut entries.snap_points);
|
||||
key_vertex_entries.append(&mut entries.key_vertices);
|
||||
key_segment_entries.append(&mut entries.key_segments);
|
||||
fill_triangle_entries.append(&mut entries.fill_triangles);
|
||||
pick_triangle_entries.append(&mut entries.pick_triangles);
|
||||
glyph_entries.append(&mut entries.glyphs);
|
||||
segment_parts.push(entries.segments);
|
||||
snap_point_parts.push(entries.snap_points);
|
||||
key_vertex_parts.push(entries.key_vertices);
|
||||
key_segment_parts.push(entries.key_segments);
|
||||
fill_triangle_parts.push(entries.fill_triangles);
|
||||
pick_triangle_parts.push(entries.pick_triangles);
|
||||
glyph_parts.push(entries.glyphs);
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let (
|
||||
((segment_entries, snap_point_entries), (key_vertex_entries, key_segment_entries)),
|
||||
((fill_triangle_entries, pick_triangle_entries), glyph_entries),
|
||||
) = rayon::join(
|
||||
|| {
|
||||
rayon::join(
|
||||
|| {
|
||||
rayon::join(
|
||||
|| flatten_entry_parts(segment_parts),
|
||||
|| flatten_entry_parts(snap_point_parts),
|
||||
)
|
||||
},
|
||||
|| {
|
||||
rayon::join(
|
||||
|| flatten_entry_parts(key_vertex_parts),
|
||||
|| flatten_entry_parts(key_segment_parts),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
|| {
|
||||
let (fill, pick) = rayon::join(
|
||||
|| flatten_entry_parts(fill_triangle_parts),
|
||||
|| flatten_entry_parts(pick_triangle_parts),
|
||||
);
|
||||
((fill, pick), flatten_entry_parts(glyph_parts))
|
||||
},
|
||||
);
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let (
|
||||
segment_entries,
|
||||
snap_point_entries,
|
||||
key_vertex_entries,
|
||||
key_segment_entries,
|
||||
fill_triangle_entries,
|
||||
pick_triangle_entries,
|
||||
glyph_entries,
|
||||
) = (
|
||||
flatten_entry_parts(segment_parts),
|
||||
flatten_entry_parts(snap_point_parts),
|
||||
flatten_entry_parts(key_vertex_parts),
|
||||
flatten_entry_parts(key_segment_parts),
|
||||
flatten_entry_parts(fill_triangle_parts),
|
||||
flatten_entry_parts(pick_triangle_parts),
|
||||
flatten_entry_parts(glyph_parts),
|
||||
);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let flatten_elapsed = flatten_started.elapsed();
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let spatial_started = std::time::Instant::now();
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let (
|
||||
@@ -806,6 +923,17 @@ impl InteractionIndex {
|
||||
SpatialSet::build(pick_triangle_entries),
|
||||
SpatialSet::build(glyph_entries),
|
||||
);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if perf {
|
||||
eprintln!(
|
||||
"[perf] interaction-index-detail total={:.1}ms handles={:.1} collect={:.1} flatten={:.1} spatial={:.1}",
|
||||
build_started.elapsed().as_secs_f64() * 1000.0,
|
||||
handles_elapsed.as_secs_f64() * 1000.0,
|
||||
collect_elapsed.as_secs_f64() * 1000.0,
|
||||
flatten_elapsed.as_secs_f64() * 1000.0,
|
||||
spatial_started.elapsed().as_secs_f64() * 1000.0,
|
||||
);
|
||||
}
|
||||
|
||||
Self {
|
||||
wires,
|
||||
|
||||
@@ -163,6 +163,14 @@ pub struct Pipeline {
|
||||
/// both patch incrementally. Shares `wire_arena_id`.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) wire_arena_mesh: Option<wire_arena::WireArena>,
|
||||
/// Chunked resident buffers for whichever arena partition exceeded one
|
||||
/// GPU buffer. `Some(false)` = regular wires, `Some(true)` = mesh edges.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) wire_arena_fallback: std::sync::Arc<Vec<WireGpu>>,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) wire_arena_fallback_kind: Option<bool>,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) wire_arena_fallback_handles: rustc_hash::FxHashSet<acadrust::Handle>,
|
||||
/// The Model content id both arenas currently mirror (`u64::MAX` = none).
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) wire_arena_id: u64,
|
||||
@@ -1455,6 +1463,12 @@ impl Pipeline {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
wire_arena_mesh: None,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
wire_arena_fallback: std::sync::Arc::new(Vec::new()),
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
wire_arena_fallback_kind: None,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
wire_arena_fallback_handles: rustc_hash::FxHashSet::default(),
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
wire_arena_id: u64::MAX,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
wire_cull_key: (u64::MAX, u64::MAX, 0, 0),
|
||||
|
||||
@@ -282,6 +282,7 @@ impl WirePipelineMode {
|
||||
|
||||
// ── GPU handle ────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WireGpu {
|
||||
pub instance_buffer: wgpu::Buffer,
|
||||
/// First instance in a shared arena buffer. Standalone buffers start at 0.
|
||||
@@ -609,6 +610,55 @@ fn build_const_bind_group(
|
||||
}
|
||||
|
||||
impl WireGpu {
|
||||
/// Native-only equivalent of [`from_run`] for an already partitioned set
|
||||
/// of borrowed wires. Used when one arena partition exceeds the 256 MB
|
||||
/// buffer limit: the compatible partition stays patchable while only the
|
||||
/// oversized side uses chunked resident buffers.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn from_run_refs(
|
||||
device: &wgpu::Device,
|
||||
wires: &[&WireModel],
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
mesh_edge: bool,
|
||||
const_bgl: &wgpu::BindGroupLayout,
|
||||
) -> Vec<Self> {
|
||||
const MAX_INSTANCES: usize =
|
||||
268_435_456 / std::mem::size_of::<WireInstance>();
|
||||
use crate::par::prelude::*;
|
||||
let per: Vec<(Vec<WireInstance>, WireConst)> = wires
|
||||
.par_iter()
|
||||
.enumerate()
|
||||
.map(|(idx, &wire)| {
|
||||
let depth = if mesh_edge {
|
||||
0.0
|
||||
} else {
|
||||
wire_draw_depth(wire, depth_map)
|
||||
};
|
||||
emit_wire_native(wire, idx as u32, wire.color, depth)
|
||||
})
|
||||
.collect();
|
||||
let mut instances: Vec<WireInstance> =
|
||||
Vec::with_capacity(per.iter().map(|(items, _)| items.len()).sum());
|
||||
let mut consts = Vec::with_capacity(per.len());
|
||||
for (mut items, constant) in per {
|
||||
instances.append(&mut items);
|
||||
consts.push(constant);
|
||||
}
|
||||
if instances.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let bind_group = build_const_bind_group(device, const_bgl, &consts);
|
||||
instances
|
||||
.chunks(MAX_INSTANCES)
|
||||
.map(|chunk| Self {
|
||||
instance_buffer: instance_buffer_mapped(device, "wire.run.hybrid.ibuf", chunk),
|
||||
first_instance: 0,
|
||||
instance_count: chunk.len() as u32,
|
||||
is_3d_mesh_edge: mesh_edge,
|
||||
const_bind_group: Some(bind_group.clone()),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Merge a run of WireModels that share scissor + mesh-edge state into one
|
||||
/// (or, past the 256 MB GPU limit, a few) instance buffer(s), then stamp
|
||||
|
||||
+156
-53
@@ -499,28 +499,48 @@ impl shader::Primitive for Primitive {
|
||||
);
|
||||
}
|
||||
}
|
||||
let fallback_touched = |mesh_edge: bool| {
|
||||
patch.map_or(true, |patch| {
|
||||
patch.changes.iter().any(|(handle, _)| {
|
||||
inner.wire_arena_fallback_handles.contains(handle)
|
||||
|| if mesh_edge {
|
||||
mesh_changed.get(handle).is_some_and(|run| !run.is_empty())
|
||||
} else {
|
||||
regular_changed
|
||||
.get(handle)
|
||||
.is_some_and(|run| !run.is_empty())
|
||||
}
|
||||
})
|
||||
})
|
||||
};
|
||||
let reg_ok = base_ok
|
||||
&& inner.wire_arena.as_mut().map_or(false, |a| {
|
||||
&& if let Some(arena) = inner.wire_arena.as_mut() {
|
||||
let patch = patch.unwrap();
|
||||
a.patch(
|
||||
arena.patch(
|
||||
queue,
|
||||
&patch.changes,
|
||||
®ular_changed,
|
||||
patch.new_handles_are_suffix,
|
||||
&vp.draw_depths,
|
||||
)
|
||||
});
|
||||
} else {
|
||||
inner.wire_arena_fallback_kind == Some(false)
|
||||
&& !fallback_touched(false)
|
||||
};
|
||||
let mesh_ok = base_ok
|
||||
&& inner.wire_arena_mesh.as_mut().map_or(false, |a| {
|
||||
&& if let Some(arena) = inner.wire_arena_mesh.as_mut() {
|
||||
let patch = patch.unwrap();
|
||||
a.patch(
|
||||
arena.patch(
|
||||
queue,
|
||||
&patch.changes,
|
||||
&mesh_changed,
|
||||
patch.new_handles_are_suffix,
|
||||
&vp.draw_depths,
|
||||
)
|
||||
});
|
||||
} else {
|
||||
inner.wire_arena_fallback_kind == Some(true)
|
||||
&& !fallback_touched(true)
|
||||
};
|
||||
if !reg_ok || !mesh_ok {
|
||||
// Initial upload or a patch that outgrew arena capacity:
|
||||
// only then pay the full regular/mesh split.
|
||||
@@ -529,14 +549,18 @@ impl shader::Primitive for Primitive {
|
||||
.filter(|w| !w.fill_tris.is_empty() && !w.fill_tris_low.is_empty())
|
||||
.filter_map(|w| w.name.parse::<u64>().ok())
|
||||
.collect();
|
||||
let regular: Vec<&crate::scene::WireModel> = vp_wires
|
||||
.iter()
|
||||
.filter(|w| {
|
||||
!w.points.is_empty()
|
||||
&& !wire_arena::is_mesh_edge(w, &mesh_names)
|
||||
})
|
||||
.collect();
|
||||
let mesh: Vec<&crate::scene::WireModel> = vp_wires
|
||||
.iter()
|
||||
.filter(|w| wire_arena::is_mesh_edge(w, &mesh_names))
|
||||
.collect();
|
||||
if !reg_ok {
|
||||
let regular: Vec<&crate::scene::WireModel> = vp_wires
|
||||
.iter()
|
||||
.filter(|w| {
|
||||
!w.points.is_empty()
|
||||
&& !wire_arena::is_mesh_edge(w, &mesh_names)
|
||||
})
|
||||
.collect();
|
||||
inner.wire_arena = WireArena::build(
|
||||
device,
|
||||
queue,
|
||||
@@ -545,12 +569,34 @@ impl shader::Primitive for Primitive {
|
||||
bgl,
|
||||
false,
|
||||
);
|
||||
if inner.wire_arena.is_none() && !regular.is_empty() {
|
||||
inner.wire_arena_fallback = std::sync::Arc::new(
|
||||
crate::scene::pipeline::WireGpu::from_run_refs(
|
||||
device,
|
||||
®ular,
|
||||
&vp.draw_depths,
|
||||
false,
|
||||
bgl,
|
||||
),
|
||||
);
|
||||
inner.wire_arena_fallback_kind = Some(false);
|
||||
inner.wire_arena_fallback_handles = regular
|
||||
.iter()
|
||||
.filter_map(|wire| {
|
||||
wire.name
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.map(acadrust::Handle::new)
|
||||
})
|
||||
.collect();
|
||||
} else if inner.wire_arena_fallback_kind == Some(false) {
|
||||
inner.wire_arena_fallback =
|
||||
std::sync::Arc::new(Vec::new());
|
||||
inner.wire_arena_fallback_kind = None;
|
||||
inner.wire_arena_fallback_handles.clear();
|
||||
}
|
||||
}
|
||||
if !mesh_ok {
|
||||
let mesh: Vec<&crate::scene::WireModel> = vp_wires
|
||||
.iter()
|
||||
.filter(|w| wire_arena::is_mesh_edge(w, &mesh_names))
|
||||
.collect();
|
||||
inner.wire_arena_mesh = WireArena::build(
|
||||
device,
|
||||
queue,
|
||||
@@ -559,15 +605,58 @@ impl shader::Primitive for Primitive {
|
||||
bgl,
|
||||
true,
|
||||
);
|
||||
if inner.wire_arena_mesh.is_none() && !mesh.is_empty() {
|
||||
inner.wire_arena_fallback = std::sync::Arc::new(
|
||||
crate::scene::pipeline::WireGpu::from_run_refs(
|
||||
device,
|
||||
&mesh,
|
||||
&vp.draw_depths,
|
||||
true,
|
||||
bgl,
|
||||
),
|
||||
);
|
||||
inner.wire_arena_fallback_kind = Some(true);
|
||||
inner.wire_arena_fallback_handles = mesh
|
||||
.iter()
|
||||
.filter_map(|wire| {
|
||||
wire.name
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.map(acadrust::Handle::new)
|
||||
})
|
||||
.collect();
|
||||
} else if inner.wire_arena_fallback_kind == Some(true) {
|
||||
inner.wire_arena_fallback =
|
||||
std::sync::Arc::new(Vec::new());
|
||||
inner.wire_arena_fallback_kind = None;
|
||||
inner.wire_arena_fallback_handles.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
_patched = reg_ok && mesh_ok;
|
||||
|
||||
if let (Some(reg), Some(me)) =
|
||||
(inner.wire_arena.as_ref(), inner.wire_arena_mesh.as_ref())
|
||||
let regular_ready = inner.wire_arena.is_some()
|
||||
|| inner.wire_arena_fallback_kind == Some(false);
|
||||
let mesh_ready = inner.wire_arena_mesh.is_some()
|
||||
|| inner.wire_arena_fallback_kind == Some(true);
|
||||
if regular_ready
|
||||
&& mesh_ready
|
||||
&& (inner.wire_arena.is_some() || inner.wire_arena_mesh.is_some())
|
||||
{
|
||||
let mut gpus = reg.wire_gpus();
|
||||
gpus.extend(me.wire_gpus());
|
||||
let mut gpus = if inner.wire_arena_fallback_kind == Some(false) {
|
||||
inner.wire_arena_fallback.as_ref().clone()
|
||||
} else {
|
||||
inner
|
||||
.wire_arena
|
||||
.as_ref()
|
||||
.map(WireArena::wire_gpus)
|
||||
.unwrap_or_default()
|
||||
};
|
||||
if inner.wire_arena_fallback_kind == Some(true) {
|
||||
gpus.extend(inner.wire_arena_fallback.iter().cloned());
|
||||
} else if let Some(arena) = inner.wire_arena_mesh.as_ref() {
|
||||
gpus.extend(arena.wire_gpus());
|
||||
}
|
||||
inner.gpu_wires = std::sync::Arc::new(gpus);
|
||||
if _patched {
|
||||
wire_arena::patch_handle_index(
|
||||
@@ -583,6 +672,9 @@ impl shader::Primitive for Primitive {
|
||||
} else {
|
||||
inner.wire_arena = None;
|
||||
inner.wire_arena_mesh = None;
|
||||
inner.wire_arena_fallback = std::sync::Arc::new(Vec::new());
|
||||
inner.wire_arena_fallback_kind = None;
|
||||
inner.wire_arena_fallback_handles.clear();
|
||||
inner.wire_arena_id = u64::MAX;
|
||||
}
|
||||
}
|
||||
@@ -594,28 +686,31 @@ impl shader::Primitive for Primitive {
|
||||
// `.cloned()` releases the immutable cache borrow before the
|
||||
// miss branch takes a mutable one.
|
||||
if !arena_served {
|
||||
let cached = pipeline.wire_buffer_cache.get(&vp.wire_content_id).cloned();
|
||||
let built = match cached {
|
||||
Some(entry) => entry,
|
||||
None => {
|
||||
let entry =
|
||||
inner.build_wire_buffers(device, &vp_wires[..], &vp.draw_depths);
|
||||
pipeline
|
||||
.wire_buffer_cache
|
||||
.insert(vp.wire_content_id, entry.clone());
|
||||
// Evict entries no slot still holds (only the cache
|
||||
// references them). An entry drawn by any pane keeps a
|
||||
// strong count ≥ 2, so this never drops live geometry.
|
||||
if pipeline.wire_buffer_cache.len() > 16 {
|
||||
let cached = pipeline
|
||||
.wire_buffer_cache
|
||||
.get(&vp.wire_content_id)
|
||||
.cloned();
|
||||
let built = match cached {
|
||||
Some(entry) => entry,
|
||||
None => {
|
||||
let entry =
|
||||
inner.build_wire_buffers(device, &vp_wires[..], &vp.draw_depths);
|
||||
pipeline
|
||||
.wire_buffer_cache
|
||||
.retain(|_, (w, _)| std::sync::Arc::strong_count(w) > 1);
|
||||
.insert(vp.wire_content_id, entry.clone());
|
||||
// Evict entries no slot still holds (only the cache
|
||||
// references them). An entry drawn by any pane keeps a
|
||||
// strong count ≥ 2, so this never drops live geometry.
|
||||
if pipeline.wire_buffer_cache.len() > 16 {
|
||||
pipeline
|
||||
.wire_buffer_cache
|
||||
.retain(|_, (w, _)| std::sync::Arc::strong_count(w) > 1);
|
||||
}
|
||||
entry
|
||||
}
|
||||
entry
|
||||
}
|
||||
};
|
||||
inner.gpu_wires = built.0;
|
||||
inner.wire_handle_index = built.1;
|
||||
};
|
||||
inner.gpu_wires = built.0;
|
||||
inner.wire_handle_index = built.1;
|
||||
} // end !arena_served
|
||||
inner.cached_wire_id = vp.wire_content_id;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -625,6 +720,8 @@ impl shader::Primitive for Primitive {
|
||||
"shared-fullupload"
|
||||
} else if _patched {
|
||||
"arena-patch"
|
||||
} else if inner.wire_arena_fallback_kind.is_some() {
|
||||
"arena-hybrid"
|
||||
} else {
|
||||
"arena-build"
|
||||
};
|
||||
@@ -724,19 +821,25 @@ impl shader::Primitive for Primitive {
|
||||
if inner.wire_arena_id == vp.wire_content_id
|
||||
&& inner.wire_cull_key != cull_key
|
||||
{
|
||||
let mut visible = inner
|
||||
.wire_arena
|
||||
.as_ref()
|
||||
.map(|arena| {
|
||||
arena.wire_gpus_visible(
|
||||
view_rot,
|
||||
eye,
|
||||
clip_size.width,
|
||||
clip_size.height,
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
if let Some(arena) = inner.wire_arena_mesh.as_ref() {
|
||||
let mut visible = if inner.wire_arena_fallback_kind == Some(false) {
|
||||
inner.wire_arena_fallback.as_ref().clone()
|
||||
} else {
|
||||
inner
|
||||
.wire_arena
|
||||
.as_ref()
|
||||
.map(|arena| {
|
||||
arena.wire_gpus_visible(
|
||||
view_rot,
|
||||
eye,
|
||||
clip_size.width,
|
||||
clip_size.height,
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
};
|
||||
if inner.wire_arena_fallback_kind == Some(true) {
|
||||
visible.extend(inner.wire_arena_fallback.iter().cloned());
|
||||
} else if let Some(arena) = inner.wire_arena_mesh.as_ref() {
|
||||
visible.extend(arena.wire_gpus_visible(
|
||||
view_rot,
|
||||
eye,
|
||||
|
||||
Reference in new issue
Block a user