forked from 1b-agent/opencadstudio-upstream
perf(render): instance block geometry
Share source buffers across block placements and render nested content with GPU instance transforms.\n\nChunk silhouettes and compact mesh material data so large drawings stay under device buffer limits and remain responsive.
This commit is contained in:
1 parent
7bfb617745
commit
4fd9b98da4
68 files changed
+3417
-547
No files matched your search
Vendored
+269
-55
@@ -146,6 +146,7 @@ pub struct BlockCache {
|
||||
expansion_prototypes: std::sync::Mutex<
|
||||
HashMap<ExpansionPrototypeKey, Arc<std::sync::Mutex<Option<Arc<CachedExpansion>>>>>,
|
||||
>,
|
||||
clip_prototypes: std::sync::Mutex<HashMap<(u64, Vec<u64>), u64>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
@@ -164,6 +165,24 @@ struct CachedExpansion {
|
||||
}
|
||||
|
||||
impl BlockCache {
|
||||
pub fn clip_source_id(
|
||||
&self,
|
||||
source_id: u64,
|
||||
polygon: &[[f64; 2]],
|
||||
translation: [f64; 3],
|
||||
) -> u64 {
|
||||
let mut shape = Vec::with_capacity(polygon.len() * 2);
|
||||
for point in polygon {
|
||||
shape.push((point[0] - translation[0]).to_bits());
|
||||
shape.push((point[1] - translation[1]).to_bits());
|
||||
}
|
||||
*self
|
||||
.clip_prototypes
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner())
|
||||
.entry((source_id, shape))
|
||||
.or_insert_with(crate::scene::model::instance_model::next_source_id)
|
||||
}
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
@@ -911,9 +930,7 @@ pub fn expand_insert(
|
||||
xform = xform.then(&scale_about_p);
|
||||
}
|
||||
let name = ins_handle.value().to_string();
|
||||
let prototype_key = if view_aabb.is_none()
|
||||
&& world_per_pixel.is_none()
|
||||
&& !ins.is_array()
|
||||
let prototype_key = if !ins.is_array()
|
||||
&& cache.prototype_blocks.contains(&ins.block_name)
|
||||
{
|
||||
Some(expansion_prototype_key(
|
||||
@@ -994,7 +1011,71 @@ pub fn expand_insert(
|
||||
}
|
||||
}
|
||||
|
||||
for offset in &crate::scene::render_graph::array_offsets(ins) {
|
||||
let ctx = ExpandCtx {
|
||||
cache,
|
||||
ins_color: ins_resolved_color,
|
||||
ins_aci,
|
||||
ins_pat_len,
|
||||
ins_pat,
|
||||
ins_lw_px,
|
||||
l0: ins_layer,
|
||||
l0_aci: ins_layer_aci,
|
||||
selected,
|
||||
pslt_factor,
|
||||
view_aabb: None,
|
||||
world_per_pixel: None,
|
||||
is_xref,
|
||||
bg_color,
|
||||
};
|
||||
let offsets = crate::scene::render_graph::array_offsets(ins);
|
||||
if offsets.len() > 1 {
|
||||
let cell_xform = |offset: &[f64; 3]| {
|
||||
if offset == &[0.0; 3] {
|
||||
xform.clone()
|
||||
} else {
|
||||
Transform::from_translation(Vector3::new(offset[0], offset[1], offset[2]))
|
||||
.then(&xform)
|
||||
}
|
||||
};
|
||||
let first_xform = cell_xform(&offsets[0]);
|
||||
let first_translation = transform_translation(&first_xform);
|
||||
let mut first_batches = Batches::default();
|
||||
expand_defn(
|
||||
defn,
|
||||
&first_xform,
|
||||
&ctx,
|
||||
&mut first_batches,
|
||||
&mut visited,
|
||||
0,
|
||||
(0.0, 1.0),
|
||||
);
|
||||
let mut first = first_batches.finalize(&name, selected, bg_color);
|
||||
for wire in &mut first {
|
||||
if wire.render_instance.is_none() {
|
||||
wire.render_instance = Some(
|
||||
crate::scene::model::instance_model::RenderInstance {
|
||||
source_id: crate::scene::model::instance_model::next_source_id(),
|
||||
translation: first_translation,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
let mut result = first.clone();
|
||||
for offset in offsets.iter().skip(1) {
|
||||
let translation = transform_translation(&cell_xform(offset));
|
||||
let delta = [
|
||||
translation[0] - first_translation[0],
|
||||
translation[1] - first_translation[1],
|
||||
translation[2] - first_translation[2],
|
||||
];
|
||||
result.extend(first.iter().map(|wire| {
|
||||
translated_prototype_wire(wire, &name, delta)
|
||||
}));
|
||||
}
|
||||
return Some(result);
|
||||
}
|
||||
|
||||
for offset in &offsets {
|
||||
let base_xform = if offset == &[0.0; 3] {
|
||||
xform.clone()
|
||||
} else {
|
||||
@@ -1003,28 +1084,21 @@ pub fn expand_insert(
|
||||
));
|
||||
translation.then(&xform)
|
||||
};
|
||||
let ctx = ExpandCtx {
|
||||
cache,
|
||||
ins_color: ins_resolved_color,
|
||||
ins_aci,
|
||||
ins_pat_len,
|
||||
ins_pat,
|
||||
ins_lw_px,
|
||||
l0: ins_layer,
|
||||
l0_aci: ins_layer_aci,
|
||||
selected,
|
||||
pslt_factor,
|
||||
view_aabb,
|
||||
world_per_pixel,
|
||||
is_xref,
|
||||
bg_color,
|
||||
};
|
||||
expand_defn(defn, &base_xform, &ctx, &mut batches, &mut visited, 0, (0.0, 1.0));
|
||||
}
|
||||
let wires = batches.finalize(&name, selected, bg_color);
|
||||
let mut wires = batches.finalize(&name, selected, bg_color);
|
||||
if let Some(guard) = prototype_guard.as_mut() {
|
||||
let translation = transform_translation(&xform);
|
||||
for wire in &mut wires {
|
||||
if wire.render_instance.is_none() {
|
||||
wire.render_instance = Some(crate::scene::model::instance_model::RenderInstance {
|
||||
source_id: crate::scene::model::instance_model::next_source_id(),
|
||||
translation,
|
||||
});
|
||||
}
|
||||
}
|
||||
let cached = Arc::new(CachedExpansion {
|
||||
translation: transform_translation(&xform),
|
||||
translation,
|
||||
wires: Arc::new(wires.clone()),
|
||||
});
|
||||
**guard = Some(cached);
|
||||
@@ -1088,9 +1162,18 @@ fn expansion_prototype_key(
|
||||
}
|
||||
}
|
||||
|
||||
fn translated_prototype_wire(source: &WireModel, name: &str, delta: [f64; 3]) -> WireModel {
|
||||
fn translated_prototype_wire(
|
||||
source: &WireModel,
|
||||
name: &str,
|
||||
delta: [f64; 3],
|
||||
) -> WireModel {
|
||||
let mut wire = source.clone();
|
||||
wire.name = name.to_string();
|
||||
if let Some(instance) = wire.render_instance.as_mut() {
|
||||
for axis in 0..3 {
|
||||
instance.translation[axis] += delta[axis];
|
||||
}
|
||||
}
|
||||
translate_double_single(&mut wire.points, &mut wire.points_low, delta);
|
||||
translate_double_single(&mut wire.fill_tris, &mut wire.fill_tris_low, delta);
|
||||
translate_double_single(&mut wire.pick_tris, &mut wire.pick_tris_low, delta);
|
||||
@@ -1182,6 +1265,39 @@ struct ExpandCtx<'a> {
|
||||
bg_color: [f32; 4],
|
||||
}
|
||||
|
||||
fn nested_prototype_key(
|
||||
block_name: &str,
|
||||
transform: &Transform,
|
||||
ctx: &ExpandCtx<'_>,
|
||||
depth_scale: f32,
|
||||
) -> NestedPrototypeKey {
|
||||
let matrix = &transform.matrix.m;
|
||||
let linear = [
|
||||
matrix[0][0].to_bits(), matrix[0][1].to_bits(), matrix[0][2].to_bits(),
|
||||
matrix[1][0].to_bits(), matrix[1][1].to_bits(), matrix[1][2].to_bits(),
|
||||
matrix[2][0].to_bits(), matrix[2][1].to_bits(), matrix[2][2].to_bits(),
|
||||
];
|
||||
let mut style = Vec::with_capacity(32);
|
||||
style.extend(ctx.ins_color.map(f32::to_bits));
|
||||
style.push(ctx.ins_pat_len.to_bits());
|
||||
style.extend(ctx.ins_pat.map(f32::to_bits));
|
||||
style.push(ctx.ins_lw_px.to_bits());
|
||||
style.extend(ctx.l0.color.map(f32::to_bits));
|
||||
style.push(ctx.l0.pat_len.to_bits());
|
||||
style.extend(ctx.l0.pat.map(f32::to_bits));
|
||||
style.push(ctx.l0.lw_px.to_bits());
|
||||
style.push(ctx.pslt_factor.to_bits());
|
||||
style.extend(ctx.bg_color.map(f32::to_bits));
|
||||
NestedPrototypeKey {
|
||||
block_name: block_name.to_string(),
|
||||
linear,
|
||||
style,
|
||||
selected: ctx.selected,
|
||||
is_xref: ctx.is_xref,
|
||||
depth_scale: depth_scale.to_bits(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Fade `color` toward `bg` by 50%, preserving alpha. Used to mark xref
|
||||
/// geometry — the hue stays recognizable but the contrast against the
|
||||
/// background drops, reading as "washed out".
|
||||
@@ -1259,6 +1375,23 @@ struct BatchEntry {
|
||||
max_y: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
struct NestedPrototypeKey {
|
||||
block_name: String,
|
||||
linear: [u64; 9],
|
||||
style: Vec<u32>,
|
||||
selected: bool,
|
||||
is_xref: bool,
|
||||
depth_scale: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct NestedPrototype {
|
||||
translation: [f64; 3],
|
||||
depth_base: f32,
|
||||
wires: Arc<Vec<WireModel>>,
|
||||
}
|
||||
|
||||
/// Hard cap on point count for a single batched WireModel. Above this the
|
||||
/// current batch is finalized (pushed into `closed`) and a fresh one is
|
||||
/// started under the same style. Each WireModel point becomes ~6 GPU
|
||||
@@ -1276,6 +1409,7 @@ struct Batches {
|
||||
/// WireModels by `clip_wires`. Appended verbatim at `finalize` (only their
|
||||
/// name/selected flag are stamped to match the host insert).
|
||||
extra_wires: Vec<WireModel>,
|
||||
nested_prototypes: HashMap<NestedPrototypeKey, NestedPrototype>,
|
||||
}
|
||||
|
||||
impl BatchEntry {
|
||||
@@ -1338,6 +1472,7 @@ impl Batches {
|
||||
depth_override: b.local_depth,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: b.fill_is_2d_solid,
|
||||
render_instance: None,
|
||||
pick_tris: b.pick_tris,
|
||||
pick_tris_low: b.pick_tris_low,
|
||||
dash_from_start: false,
|
||||
@@ -1529,54 +1664,133 @@ fn expand_defn(
|
||||
d_range.0 + nref.local_rank * d_range.1,
|
||||
d_range.1 / (nested_defn.child_count.max(1) as f32 + 1.0),
|
||||
);
|
||||
let composed_for = |offset: &[f64; 3]| {
|
||||
if offset == &[0.0; 3] {
|
||||
nref.xform.then(accum_xform)
|
||||
} else {
|
||||
Transform::from_translation(Vector3::new(
|
||||
offset[0], offset[1], offset[2],
|
||||
))
|
||||
.then(&nref.xform)
|
||||
.then(accum_xform)
|
||||
}
|
||||
};
|
||||
if let Some(cp) = &nref.clip_poly {
|
||||
// XCLIP'd nested insert: expand into an isolated batch set,
|
||||
// finalize to whole wires, clip them to the boundary mapped
|
||||
// into world by the accumulated transform, then carry the
|
||||
// clipped wires out via `extra_wires`. Single instance only —
|
||||
// an array of clipped nested inserts is vanishingly rare, so
|
||||
// the array case is not handled (boundary placement per
|
||||
// instance would differ).
|
||||
let composed = nref.xform.then(accum_xform);
|
||||
let mut sub = Batches::default();
|
||||
expand_defn(
|
||||
nested_defn,
|
||||
&composed,
|
||||
&inner_ctx,
|
||||
&mut sub,
|
||||
visited,
|
||||
depth + 1,
|
||||
nested_range,
|
||||
);
|
||||
let mut wires = sub.finalize("", ctx.selected, ctx.bg_color);
|
||||
let world_poly: Vec<[f64; 2]> = cp
|
||||
let base_composed = nref.xform.then(accum_xform);
|
||||
let base_translation = transform_translation(&base_composed);
|
||||
let base_poly: Vec<[f64; 2]> = cp
|
||||
.iter()
|
||||
.map(|&[x, y]| {
|
||||
let w = accum_xform.apply(Vector3::new(x, y, 0.0));
|
||||
[w.x, w.y]
|
||||
})
|
||||
.collect();
|
||||
crate::scene::pick::xclip::clip_wires(&mut wires, &world_poly);
|
||||
out.extra_wires.append(&mut wires);
|
||||
} else {
|
||||
let mut first: Option<(Vec<WireModel>, [f64; 3])> = None;
|
||||
for offset in &nref.instance_offsets {
|
||||
let composed = if offset == &[0.0; 3] {
|
||||
nref.xform.then(accum_xform)
|
||||
} else {
|
||||
let translation = Transform::from_translation(Vector3::new(
|
||||
offset[0], offset[1], offset[2],
|
||||
));
|
||||
translation.then(&nref.xform).then(accum_xform)
|
||||
};
|
||||
let composed = composed_for(offset);
|
||||
let translation = transform_translation(&composed);
|
||||
if let Some((source, source_translation)) = &first {
|
||||
let delta = [
|
||||
translation[0] - source_translation[0],
|
||||
translation[1] - source_translation[1],
|
||||
translation[2] - source_translation[2],
|
||||
];
|
||||
out.extra_wires.extend(source.iter().map(|wire| {
|
||||
translated_prototype_wire(wire, "", delta)
|
||||
}));
|
||||
continue;
|
||||
}
|
||||
let mut sub = Batches::default();
|
||||
expand_defn(
|
||||
nested_defn,
|
||||
&composed,
|
||||
&inner_ctx,
|
||||
out,
|
||||
&mut sub,
|
||||
visited,
|
||||
depth + 1,
|
||||
nested_range,
|
||||
);
|
||||
let mut wires = sub.finalize("", ctx.selected, ctx.bg_color);
|
||||
let poly_delta = [
|
||||
translation[0] - base_translation[0],
|
||||
translation[1] - base_translation[1],
|
||||
];
|
||||
let world_poly: Vec<[f64; 2]> = base_poly
|
||||
.iter()
|
||||
.map(|point| [point[0] + poly_delta[0], point[1] + poly_delta[1]])
|
||||
.collect();
|
||||
crate::scene::pick::xclip::clip_wires(&mut wires, &world_poly);
|
||||
for wire in &mut wires {
|
||||
wire.render_instance = Some(
|
||||
crate::scene::model::instance_model::RenderInstance {
|
||||
source_id: crate::scene::model::instance_model::next_source_id(),
|
||||
translation,
|
||||
},
|
||||
);
|
||||
}
|
||||
out.extra_wires.extend(wires.iter().cloned());
|
||||
first = Some((wires, translation));
|
||||
}
|
||||
} else {
|
||||
for offset in &nref.instance_offsets {
|
||||
let composed = composed_for(offset);
|
||||
let translation = transform_translation(&composed);
|
||||
let key = nested_prototype_key(
|
||||
&nref.block_name,
|
||||
&composed,
|
||||
&inner_ctx,
|
||||
nested_range.1,
|
||||
);
|
||||
if let Some(cached) = out.nested_prototypes.get(&key).cloned() {
|
||||
let delta = [
|
||||
translation[0] - cached.translation[0],
|
||||
translation[1] - cached.translation[1],
|
||||
translation[2] - cached.translation[2],
|
||||
];
|
||||
let depth_delta = nested_range.0 - cached.depth_base;
|
||||
out.extra_wires.extend(cached.wires.iter().map(|wire| {
|
||||
let mut wire = translated_prototype_wire(
|
||||
wire,
|
||||
"",
|
||||
delta,
|
||||
);
|
||||
if let Some(depth) = wire.depth_override.as_mut() {
|
||||
*depth += depth_delta;
|
||||
}
|
||||
wire
|
||||
}));
|
||||
} else {
|
||||
let mut sub = Batches::default();
|
||||
expand_defn(
|
||||
nested_defn,
|
||||
&composed,
|
||||
&inner_ctx,
|
||||
&mut sub,
|
||||
visited,
|
||||
depth + 1,
|
||||
nested_range,
|
||||
);
|
||||
let mut wires = sub.finalize("", ctx.selected, ctx.bg_color);
|
||||
for wire in &mut wires {
|
||||
if wire.render_instance.is_none() {
|
||||
wire.render_instance = Some(
|
||||
crate::scene::model::instance_model::RenderInstance {
|
||||
source_id: crate::scene::model::instance_model::next_source_id(),
|
||||
translation,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
out.extra_wires.extend(wires.iter().cloned());
|
||||
out.nested_prototypes.insert(
|
||||
key,
|
||||
NestedPrototype {
|
||||
translation,
|
||||
depth_base: nested_range.0,
|
||||
wires: Arc::new(wires),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
visited.pop();
|
||||
|
||||
Reference in new issue
Block a user