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(
|
pub fn upload_circles(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
@@ -2425,6 +2425,16 @@ impl Pipeline {
|
|||||||
instances.push(inst);
|
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() {
|
if instances.is_empty() {
|
||||||
vec![]
|
vec![]
|
||||||
} else {
|
} else {
|
||||||
@@ -2432,12 +2442,12 @@ impl Pipeline {
|
|||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
"viewer.circles",
|
"viewer.circles",
|
||||||
&instances,
|
instances,
|
||||||
)]
|
)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Upload GPU-instanced analytical ellipses.
|
/// Upload GPU-instanced analytical ellipses from a wire slice.
|
||||||
pub fn upload_ellipses(
|
pub fn upload_ellipses(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
@@ -2455,6 +2465,16 @@ impl Pipeline {
|
|||||||
instances.push(inst);
|
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() {
|
if instances.is_empty() {
|
||||||
vec![]
|
vec![]
|
||||||
} else {
|
} else {
|
||||||
@@ -2462,7 +2482,7 @@ impl Pipeline {
|
|||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
"viewer.ellipses",
|
"viewer.ellipses",
|
||||||
&instances,
|
instances,
|
||||||
)]
|
)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2490,48 +2510,42 @@ impl Pipeline {
|
|||||||
// wire order — already sorted by draw order — is preserved; depth bias
|
// wire order — already sorted by draw order — is preserved; depth bias
|
||||||
// and alpha blending both depend on it. Scissor and mesh-edge stay
|
// and alpha blending both depend on it. Scissor and mesh-edge stay
|
||||||
// grouping keys because the draw loop sets one scissor per batch and
|
// 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 batches: Vec<WireGpu> = Vec::new();
|
||||||
let mut block_wires: Vec<&WireModel> = Vec::new();
|
let mut block_wires: Vec<&WireModel> = Vec::new();
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while i < wires.len() {
|
while i < wires.len() {
|
||||||
if !wires[i].display_visible {
|
let kind = wire_arena::classify_wire(&wires[i]);
|
||||||
i += 1;
|
match kind {
|
||||||
continue;
|
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(
|
let block_batches = BlockWireGpu::from_wires(
|
||||||
device,
|
device,
|
||||||
|
|||||||
@@ -120,19 +120,147 @@ fn handle_of(w: &WireModel) -> Option<Handle> {
|
|||||||
crate::scene::Scene::handle_from_wire_name(&w.name)
|
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.
|
/// Use the same regular/mesh partition for full uploads and changed runs.
|
||||||
/// Instanced blocks are uploaded separately by `BlockWireGpu`.
|
/// Instanced blocks are uploaded separately by `BlockWireGpu`.
|
||||||
pub fn split_wires(wires: &[WireModel]) -> (Vec<&WireModel>, Vec<&WireModel>) {
|
pub fn split_wires(wires: &[WireModel]) -> (Vec<&WireModel>, Vec<&WireModel>) {
|
||||||
wires
|
let mut regular = Vec::new();
|
||||||
.iter()
|
let mut mesh = Vec::new();
|
||||||
.filter(|wire| {
|
for wire in wires {
|
||||||
wire.display_visible
|
match classify_wire(wire) {
|
||||||
&& !wire.points.is_empty()
|
WireKind::Regular => regular.push(wire),
|
||||||
&& wire.render_instance.is_none()
|
WireKind::MeshEdge => mesh.push(wire),
|
||||||
&& 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)
|
(regular, mesh)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// True when appending a new entity at the tail could change the image, so the
|
/// 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());
|
gpus.extend(arena.wire_gpus());
|
||||||
}
|
}
|
||||||
inner.gpu_wires = std::sync::Arc::new(gpus);
|
inner.gpu_wires = std::sync::Arc::new(gpus);
|
||||||
// Blocks keep their own cached geometry and placements.
|
let partitioned =
|
||||||
let instanced: Vec<&crate::scene::WireModel> = vp_wires
|
wire_arena::partition_wires(&vp_wires, &draw_depths);
|
||||||
.iter()
|
|
||||||
.filter(|w| w.render_instance.is_some())
|
|
||||||
.collect();
|
|
||||||
inner.gpu_block_wires = std::sync::Arc::new(
|
inner.gpu_block_wires = std::sync::Arc::new(
|
||||||
inner.upload_block_wires(
|
inner.upload_block_wires(
|
||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
&instanced,
|
&partitioned.instanced,
|
||||||
&draw_depths,
|
&draw_depths,
|
||||||
&mut pipeline.block_geometry,
|
&mut pipeline.block_geometry,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
inner.gpu_circles = std::sync::Arc::new(
|
inner.gpu_circles = std::sync::Arc::new(
|
||||||
inner.upload_circles(
|
inner.upload_circles_from_instances(
|
||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
&vp_wires,
|
&partitioned.circle_instances,
|
||||||
&draw_depths,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
inner.gpu_ellipses = std::sync::Arc::new(
|
inner.gpu_ellipses = std::sync::Arc::new(
|
||||||
inner.upload_ellipses(
|
inner.upload_ellipses_from_instances(
|
||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
&vp_wires,
|
&partitioned.ellipse_instances,
|
||||||
&draw_depths,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if _patched {
|
if _patched {
|
||||||
|
|||||||
Reference in new issue
Block a user