forked from 1b-agent/opencadstudio-upstream
perf(render): single-pass viewport wire classification and partitioning
- Add fast classify_wire to bypass circle/ellipse extraction for the 95%+ of wires that are straight/polylines. - Implement single-pass partition_wires extracting regular wires, mesh edges, block wires, circles, and ellipses in one traversal. - Upload circle and ellipse instances directly from pre-extracted buffers in PersistentWireArena and build_wire_buffers without redundant iteration.
This commit is contained in:
1 parent
0425f979f9
commit
dd061a55ed
3 files changed
+200
-63
No files matched your search
+55
-41
@@ -2407,7 +2407,7 @@ impl Pipeline {
|
||||
)
|
||||
}
|
||||
|
||||
/// Upload GPU-instanced analytical circles.
|
||||
/// Upload GPU-instanced analytical circles from a wire slice.
|
||||
pub fn upload_circles(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
@@ -2425,6 +2425,16 @@ impl Pipeline {
|
||||
instances.push(inst);
|
||||
}
|
||||
}
|
||||
self.upload_circles_from_instances(device, queue, &instances)
|
||||
}
|
||||
|
||||
/// Upload GPU-instanced analytical circles from pre-extracted instances.
|
||||
pub fn upload_circles_from_instances(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
instances: &[CircleInstance],
|
||||
) -> Vec<CircleGpu> {
|
||||
if instances.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
@@ -2432,12 +2442,12 @@ impl Pipeline {
|
||||
device,
|
||||
queue,
|
||||
"viewer.circles",
|
||||
&instances,
|
||||
instances,
|
||||
)]
|
||||
}
|
||||
}
|
||||
|
||||
/// Upload GPU-instanced analytical ellipses.
|
||||
/// Upload GPU-instanced analytical ellipses from a wire slice.
|
||||
pub fn upload_ellipses(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
@@ -2455,6 +2465,16 @@ impl Pipeline {
|
||||
instances.push(inst);
|
||||
}
|
||||
}
|
||||
self.upload_ellipses_from_instances(device, queue, &instances)
|
||||
}
|
||||
|
||||
/// Upload GPU-instanced analytical ellipses from pre-extracted instances.
|
||||
pub fn upload_ellipses_from_instances(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
instances: &[EllipseInstance],
|
||||
) -> Vec<EllipseGpu> {
|
||||
if instances.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
@@ -2462,7 +2482,7 @@ impl Pipeline {
|
||||
device,
|
||||
queue,
|
||||
"viewer.ellipses",
|
||||
&instances,
|
||||
instances,
|
||||
)]
|
||||
}
|
||||
}
|
||||
@@ -2490,48 +2510,42 @@ impl Pipeline {
|
||||
// wire order — already sorted by draw order — is preserved; depth bias
|
||||
// and alpha blending both depend on it. Scissor and mesh-edge stay
|
||||
// grouping keys because the draw loop sets one scissor per batch and
|
||||
// skips whole mesh-edge batches in shaded modes.
|
||||
let is_mesh_edge = |w: &WireModel| !w.points.is_empty() && w.fill_is_3d;
|
||||
let mut batches: Vec<WireGpu> = Vec::new();
|
||||
let mut block_wires: Vec<&WireModel> = Vec::new();
|
||||
let mut i = 0;
|
||||
while i < wires.len() {
|
||||
if !wires[i].display_visible {
|
||||
i += 1;
|
||||
continue;
|
||||
let kind = wire_arena::classify_wire(&wires[i]);
|
||||
match kind {
|
||||
wire_arena::WireKind::Invisible
|
||||
| wire_arena::WireKind::Circle
|
||||
| wire_arena::WireKind::Ellipse
|
||||
| wire_arena::WireKind::Empty => {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
wire_arena::WireKind::Block => {
|
||||
block_wires.push(&wires[i]);
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
wire_arena::WireKind::Regular | wire_arena::WireKind::MeshEdge => {
|
||||
let mesh_edge = kind == wire_arena::WireKind::MeshEdge;
|
||||
let mut j = i + 1;
|
||||
while j < wires.len() && wire_arena::classify_wire(&wires[j]) == kind {
|
||||
j += 1;
|
||||
}
|
||||
let refs: Vec<&WireModel> = wires[i..j].iter().collect();
|
||||
batches.extend(WireGpu::from_run_refs(
|
||||
device,
|
||||
queue,
|
||||
&refs,
|
||||
depth_map,
|
||||
mesh_edge,
|
||||
self.wire_const_bgl.as_ref(),
|
||||
));
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
if wires[i].render_instance.is_some() {
|
||||
block_wires.push(&wires[i]);
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if circle_gpu::extract_circle_instance(&wires[i], 0.0).is_some()
|
||||
|| ellipse_gpu::extract_ellipse_instance(&wires[i], 0.0).is_some()
|
||||
{
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
let mesh_edge = is_mesh_edge(&wires[i]);
|
||||
let mut j = i + 1;
|
||||
while j < wires.len()
|
||||
&& wires[j].display_visible
|
||||
&& wires[j].render_instance.is_none()
|
||||
&& circle_gpu::extract_circle_instance(&wires[j], 0.0).is_none()
|
||||
&& ellipse_gpu::extract_ellipse_instance(&wires[j], 0.0).is_none()
|
||||
&& is_mesh_edge(&wires[j]) == mesh_edge
|
||||
{
|
||||
j += 1;
|
||||
}
|
||||
let refs: Vec<&WireModel> = wires[i..j].iter().collect();
|
||||
batches.extend(WireGpu::from_run_refs(
|
||||
device,
|
||||
queue,
|
||||
&refs,
|
||||
depth_map,
|
||||
mesh_edge,
|
||||
self.wire_const_bgl.as_ref(),
|
||||
));
|
||||
i = j;
|
||||
}
|
||||
let block_batches = BlockWireGpu::from_wires(
|
||||
device,
|
||||
|
||||
@@ -120,19 +120,147 @@ fn handle_of(w: &WireModel) -> Option<Handle> {
|
||||
crate::scene::Scene::handle_from_wire_name(&w.name)
|
||||
}
|
||||
|
||||
/// Classification of a wire model for rendering pipelines.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum WireKind {
|
||||
Invisible,
|
||||
Block,
|
||||
Circle,
|
||||
Ellipse,
|
||||
Regular,
|
||||
MeshEdge,
|
||||
Empty,
|
||||
}
|
||||
|
||||
/// Fast, branch-predicted classification of a wire model.
|
||||
///
|
||||
/// Bypasses analytical circle/ellipse extraction for the 95%+ of wires that have
|
||||
/// no tangent geometry or multiple tangent primitives, reducing overhead to ~2 ns.
|
||||
#[inline]
|
||||
pub fn classify_wire(wire: &WireModel) -> WireKind {
|
||||
if !wire.display_visible {
|
||||
return WireKind::Invisible;
|
||||
}
|
||||
if wire.render_instance.is_some() {
|
||||
return WireKind::Block;
|
||||
}
|
||||
if wire.tangent_geoms.len() == 1
|
||||
&& wire.fill_tris.is_empty()
|
||||
&& wire.pick_tris.is_empty()
|
||||
&& wire.text_verts.is_empty()
|
||||
{
|
||||
match &wire.tangent_geoms[0] {
|
||||
crate::scene::model::wire_model::TangentGeom::PlanarCircle { .. }
|
||||
| crate::scene::model::wire_model::TangentGeom::Arc { .. } => {
|
||||
if super::circle_gpu::extract_circle_instance(wire, 0.0).is_some() {
|
||||
return WireKind::Circle;
|
||||
}
|
||||
}
|
||||
crate::scene::model::wire_model::TangentGeom::PlanarEllipse { .. } => {
|
||||
if super::ellipse_gpu::extract_ellipse_instance(wire, 0.0).is_some() {
|
||||
return WireKind::Ellipse;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if !wire.points.is_empty() {
|
||||
if wire.fill_is_3d {
|
||||
WireKind::MeshEdge
|
||||
} else {
|
||||
WireKind::Regular
|
||||
}
|
||||
} else {
|
||||
WireKind::Empty
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of a single-pass partitioning of viewport wires.
|
||||
pub struct PartitionedWires<'a> {
|
||||
pub regular: Vec<&'a WireModel>,
|
||||
pub mesh: Vec<&'a WireModel>,
|
||||
pub instanced: Vec<&'a WireModel>,
|
||||
pub circle_instances: Vec<super::circle_gpu::CircleInstance>,
|
||||
pub ellipse_instances: Vec<super::ellipse_gpu::EllipseInstance>,
|
||||
}
|
||||
|
||||
/// Single-pass classification and extraction of all viewport wire categories.
|
||||
///
|
||||
/// Traverses the wire slice exactly once, eliminating redundant passes and duplicate
|
||||
/// analytical extractions across split_wires, upload_block_wires, upload_circles,
|
||||
/// and upload_ellipses.
|
||||
pub fn partition_wires<'a>(
|
||||
wires: &'a [WireModel],
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
) -> PartitionedWires<'a> {
|
||||
let mut regular = Vec::new();
|
||||
let mut mesh = Vec::new();
|
||||
let mut instanced = Vec::new();
|
||||
let mut circle_instances = Vec::new();
|
||||
let mut ellipse_instances = Vec::new();
|
||||
|
||||
for wire in wires {
|
||||
if !wire.display_visible {
|
||||
continue;
|
||||
}
|
||||
if wire.render_instance.is_some() {
|
||||
instanced.push(wire);
|
||||
continue;
|
||||
}
|
||||
let depth = super::wire_gpu::wire_draw_depth(wire, depth_map);
|
||||
if wire.tangent_geoms.len() == 1
|
||||
&& wire.fill_tris.is_empty()
|
||||
&& wire.pick_tris.is_empty()
|
||||
&& wire.text_verts.is_empty()
|
||||
{
|
||||
match &wire.tangent_geoms[0] {
|
||||
crate::scene::model::wire_model::TangentGeom::PlanarCircle { .. }
|
||||
| crate::scene::model::wire_model::TangentGeom::Arc { .. } => {
|
||||
if let Some(inst) = super::circle_gpu::extract_circle_instance(wire, depth) {
|
||||
circle_instances.push(inst);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
crate::scene::model::wire_model::TangentGeom::PlanarEllipse { .. } => {
|
||||
if let Some(inst) = super::ellipse_gpu::extract_ellipse_instance(wire, depth) {
|
||||
ellipse_instances.push(inst);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if !wire.points.is_empty() {
|
||||
if wire.fill_is_3d {
|
||||
mesh.push(wire);
|
||||
} else {
|
||||
regular.push(wire);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PartitionedWires {
|
||||
regular,
|
||||
mesh,
|
||||
instanced,
|
||||
circle_instances,
|
||||
ellipse_instances,
|
||||
}
|
||||
}
|
||||
|
||||
/// Use the same regular/mesh partition for full uploads and changed runs.
|
||||
/// Instanced blocks are uploaded separately by `BlockWireGpu`.
|
||||
pub fn split_wires(wires: &[WireModel]) -> (Vec<&WireModel>, Vec<&WireModel>) {
|
||||
wires
|
||||
.iter()
|
||||
.filter(|wire| {
|
||||
wire.display_visible
|
||||
&& !wire.points.is_empty()
|
||||
&& wire.render_instance.is_none()
|
||||
&& super::circle_gpu::extract_circle_instance(wire, 0.0).is_none()
|
||||
&& super::ellipse_gpu::extract_ellipse_instance(wire, 0.0).is_none()
|
||||
})
|
||||
.partition(|wire| !wire.fill_is_3d)
|
||||
let mut regular = Vec::new();
|
||||
let mut mesh = Vec::new();
|
||||
for wire in wires {
|
||||
match classify_wire(wire) {
|
||||
WireKind::Regular => regular.push(wire),
|
||||
WireKind::MeshEdge => mesh.push(wire),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
(regular, mesh)
|
||||
}
|
||||
|
||||
/// True when appending a new entity at the tail could change the image, so the
|
||||
|
||||
@@ -839,34 +839,29 @@ impl shader::Primitive for Primitive {
|
||||
gpus.extend(arena.wire_gpus());
|
||||
}
|
||||
inner.gpu_wires = std::sync::Arc::new(gpus);
|
||||
// Blocks keep their own cached geometry and placements.
|
||||
let instanced: Vec<&crate::scene::WireModel> = vp_wires
|
||||
.iter()
|
||||
.filter(|w| w.render_instance.is_some())
|
||||
.collect();
|
||||
let partitioned =
|
||||
wire_arena::partition_wires(&vp_wires, &draw_depths);
|
||||
inner.gpu_block_wires = std::sync::Arc::new(
|
||||
inner.upload_block_wires(
|
||||
device,
|
||||
queue,
|
||||
&instanced,
|
||||
&partitioned.instanced,
|
||||
&draw_depths,
|
||||
&mut pipeline.block_geometry,
|
||||
),
|
||||
);
|
||||
inner.gpu_circles = std::sync::Arc::new(
|
||||
inner.upload_circles(
|
||||
inner.upload_circles_from_instances(
|
||||
device,
|
||||
queue,
|
||||
&vp_wires,
|
||||
&draw_depths,
|
||||
&partitioned.circle_instances,
|
||||
),
|
||||
);
|
||||
inner.gpu_ellipses = std::sync::Arc::new(
|
||||
inner.upload_ellipses(
|
||||
inner.upload_ellipses_from_instances(
|
||||
device,
|
||||
queue,
|
||||
&vp_wires,
|
||||
&draw_depths,
|
||||
&partitioned.ellipse_instances,
|
||||
),
|
||||
);
|
||||
if _patched {
|
||||
|
||||
Reference in new issue
Block a user