0020. Spatial Viewport Culling (Rect::intersects), Batched Geometry & Frame Budgets
Skipping off-screen PCB pads and copper traces before Painter tessellation using axis-aligned bounding-box intersection (Rect::intersects), pre-allocating visible batches with Vec::with_capacity, and proving spatial-query equivalence with proptest.
Why Immediate-Mode CAD Canvases Need Viewport Frustum Culling
On a dense multi-layer PCB with 50,000+ SMD pads and trace segments, zoomed in on a single microcontroller (U1), 95% of the board geometry lies outside the visible camera rectangle (self.view.pcb_scene_rect). If paint_board blindly submits every trace and pad to egui::Painter on every frame, egui still has to transform coordinates and tessellate thousands of off-screen primitives into vertex/index meshes before the GPU clips them.Axis-Aligned Bounding Box (AABB) Culling: Testing primitive.bounds_mm().intersects(viewport_mm) costs only four float comparisons (self.min.x <= other.max.x && other.min.x <= self.max.x && ...), which is orders of magnitude cheaper than tessellating stroke geometry.
In crates/pcb-doc (L1), we give both SmdPad and TraceSegment exact millimetre bounding boxes:
- Pad AABB (
SmdPad::bounds_mm):Rect::from_center_size(self.center_mm, self.size_mm). - Stroke-Expanded Trace AABB (
TraceSegment::bounds_mm): A trace fromstarttoendwith copper thicknesswidth_mmextendswidth_mm * 0.5beyond its centreline endpoints. We computeRect::from_two_pos(self.start, self.end).expand(self.width_mm * 0.5)so thick power traces partially overlapping the viewport edge are never popped out prematurely! - Tier 7 Frame Budget Gate (
check_cull_budget): Enforces that spatial culling stays within a deterministic microsecond budget (measured_us <= budget.target_max_us).
flowchart LR
SceneRect["Active Camera Frustum<br/>(pcb_scene_rect in mm)"] --> Pads["BoardModel::culled_pads_in_rect"]
SceneRect --> Traces["BoardModel::culled_traces_in_rect"]
Pads --> Stats["ViewportCullStats<br/>Visible: X/Y pads, A/B traces"]
Traces --> Stats
Stats --> Budget["Tier 7 PerfBudget Check<br/>check_cull_budget(measured_us, budget)"]
style SceneRect fill:#141a21,stroke:#3b8eea,color:#e6edf3
style Stats fill:#14231c,stroke:#3dd68c,color:#e6edf3
style Budget fill:#1c1c24,stroke:#f0e68c,color:#e6edf3
2. Anatomy of Spatial Viewport Culling & Budgets: Block by Block
To avoid repeated heap reallocations during per-frame culling, culled_pads_in_rect and culled_traces_in_rect pre-allocate with Vec::with_capacity and collect only primitives whose bounds_mm().intersects(viewport_mm) is true.
Let's walk through the new code in crates/pcb-doc/src/lib.rs and src/main.rs across four logical blocks, then inspect the complete assembled listing in context.
Block 1: Stroke-Expanded Trace Bounds, ViewportCullStats & check_cull_budget (crates/pcb-doc/src/lib.rs)
impl TraceSegment {
pub fn bounds_mm(&self) -> Rect {
Rect::from_two_pos(self.start, self.end).expand(self.width_mm * 0.5)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ViewportCullStats {
pub visible_pads: usize,
pub total_pads: usize,
pub visible_traces: usize,
pub total_traces: usize,
}
impl ViewportCullStats {
pub fn culled_count(&self) -> usize {
(self.total_pads - self.visible_pads) + (self.total_traces - self.visible_traces)
}
pub fn summary_badge(&self) -> String {
format!(
"Cull: {}/{} pads, {}/{} traces ({} skipped)",
self.visible_pads,
self.total_pads,
self.visible_traces,
self.total_traces,
self.culled_count()
)
}
}
pub fn check_cull_budget(measured_us: u64, target_max_us: u64) -> Result<u64, u64> {
if measured_us <= target_max_us {
Ok(measured_us)
} else {
Err(measured_us)
}
}
Rect::from_two_pos(self.start, self.end).expand(self.width_mm * 0.5):Rect::from_two_posnormalizes arbitrary diagonal or axis-aligned segment endpoints into(min, max)millimetre coordinates, and.expand(self.width_mm * 0.5)inflates all four edges by half the copper trace thickness so thick power traces touching the viewport border are never culled early.ViewportCullStats::culled_count&summary_badge: Computes the exact number of skipped primitives across both SMD pads and trace segments and formats a deterministic telemetry string ("Cull: X/Y pads, A/B traces (N skipped)") for the session panel and headless UI assertions.check_cull_budget(measured_us, target_max_us): A Tier 7 performance-budget gate returningOk(measured_us)whenmeasured_us <= target_max_usandErr(measured_us)when a spatial query exceeds its microsecond ceiling.
Block 2: Pre-Allocated Frustum Culling Queries on BoardModel (crates/pcb-doc/src/lib.rs)
impl BoardModel {
pub fn culled_pads_in_rect(&self, viewport_mm: Rect) -> Vec<SmdPad> {
let mut out = Vec::with_capacity(self.pads.len());
for pad in self.pads.iter() {
if pad.bounds_mm().intersects(viewport_mm) {
out.push(*pad);
}
}
out
}
pub fn culled_traces_in_rect(&self, viewport_mm: Rect) -> Vec<TraceSegment> {
let mut out = Vec::with_capacity(self.traces.len());
for seg in self.traces.iter() {
if seg.bounds_mm().intersects(viewport_mm) {
out.push(*seg);
}
}
out
}
pub fn cull_stats_for_rect(&self, viewport_mm: Rect) -> ViewportCullStats {
ViewportCullStats {
visible_pads: self.culled_pads_in_rect(viewport_mm).len(),
total_pads: self.pads.len(),
visible_traces: self.culled_traces_in_rect(viewport_mm).len(),
total_traces: self.traces.len(),
}
}
}
Vec::with_capacity(self.pads.len()): Allocates a single contiguous buffer upfront so pushing visibleCopyprimitives (*padand*seg) never triggers intermediate vector growth reallocations during the frame loop.pad.bounds_mm().intersects(viewport_mm): Usesemath::Rect::intersectsto test 2D axis-aligned bounding-box overlap inO(1)time per primitive before any screen-spaceTSTransformmath oregui::Painterstroke tessellation occurs.cull_stats_for_rect(&self, viewport_mm: Rect): Aggregatesvisible_pads,total_pads,visible_traces, andtotal_tracesfor any camera frustum rectangle.
Block 3: Viewport Culling Toggle & Live Telemetry Badge (src/main.rs)
// Added field on `pub struct ViewState` (defaults to `true` in `ViewState::default()`):
pub enable_viewport_culling: bool,
// Added method on `impl PcbReviewerApp`:
/// Computes active viewport culling telemetry based on `self.view.enable_viewport_culling`.
pub fn active_cull_stats(&self) -> ViewportCullStats {
if self.view.enable_viewport_culling {
self.board.cull_stats_for_rect(self.view.pcb_scene_rect)
} else {
ViewportCullStats {
visible_pads: self.board.pads.len(),
total_pads: self.board.pads.len(),
visible_traces: self.board.traces.len(),
total_traces: self.board.traces.len(),
}
}
}
// Inside `PcbReviewerApp::render_session_panel`:
ui.checkbox(
&mut self.view.enable_viewport_culling,
"Enable Viewport Culling",
);
ui.label(self.active_cull_stats().summary_badge());
enable_viewport_culling: bool: Stored inViewStateand enabled (true) by default so the reviewer culls off-screen geometry out of the box while still letting engineers toggle culling off to compare full-board vs. frustum-culled counts.PcbReviewerApp::active_cull_stats(&self): Whenenable_viewport_cullingistrue, queriesself.board.cull_stats_for_rect(self.view.pcb_scene_rect); whenfalse, reports all pads and traces as visible (0 skipped).- Session Panel Telemetry: Binds
ui.checkbox(&mut self.view.enable_viewport_culling, "Enable Viewport Culling")and rendersui.label(self.active_cull_stats().summary_badge())so camera pan/zoom or DRC jump-to-violation updates the skipped count immediately.
Block 4: proptest! Spatial-Query Equivalence & Budget Unit Tests (crates/pcb-doc/src/lib.rs & src/main.rs)
// Inside `proptest!` in `crates/pcb-doc/src/lib.rs`:
#[test]
fn prop_spatial_culling_matches_reference_and_preserves_counts(
min_x in 0.0_f32..80.0_f32,
min_y in 0.0_f32..50.0_f32,
w in 5.0_f32..60.0_f32,
h in 5.0_f32..40.0_f32,
) {
let board = BoardModel::default();
let viewport = Rect::from_min_size(pos2(min_x, min_y), vec2(w, h));
let expected_pads: Vec<SmdPad> = board
.pads
.iter()
.copied()
.filter(|p| p.bounds_mm().intersects(viewport))
.collect();
let expected_traces: Vec<TraceSegment> = board
.traces
.iter()
.copied()
.filter(|t| t.bounds_mm().intersects(viewport))
.collect();
let culled_pads = board.culled_pads_in_rect(viewport);
let culled_traces = board.culled_traces_in_rect(viewport);
let stats = board.cull_stats_for_rect(viewport);
prop_assert_eq!(&culled_pads, &expected_pads);
prop_assert_eq!(&culled_traces, &expected_traces);
prop_assert_eq!(stats.visible_pads, expected_pads.len());
prop_assert_eq!(stats.visible_traces, expected_traces.len());
prop_assert_eq!(
stats.visible_pads + stats.visible_traces + stats.culled_count(),
board.pads.len() + board.traces.len()
);
}
#[test]
fn viewport_culling_bounds_stats_and_perf_budget_are_exact() {
let seg = TraceSegment {
start: pos2(10.0, 20.0),
end: pos2(30.0, 24.0),
width_mm: 4.0,
layer: PcbLayer::TopCopper,
net: Some(NetId(1)),
};
assert_eq!(
seg.bounds_mm(),
Rect::from_min_max(pos2(8.0, 18.0), pos2(32.0, 26.0))
);
let board = BoardModel::default();
let tight_rect = Rect::from_min_max(pos2(12.0, 14.0), pos2(25.0, 24.0));
let stats = board.cull_stats_for_rect(tight_rect);
assert_eq!(stats.visible_pads, 1);
assert_eq!(stats.total_pads, 3);
assert_eq!(stats.visible_traces, 2);
assert_eq!(stats.total_traces, 6);
assert_eq!(stats.culled_count(), 6);
assert_eq!(
stats.summary_badge(),
"Cull: 1/3 pads, 2/6 traces (6 skipped)"
);
assert_eq!(check_cull_budget(180, 500), Ok(180));
assert_eq!(check_cull_budget(500, 500), Ok(500));
assert_eq!(check_cull_budget(501, 500), Err(501));
}
prop_spatial_culling_matches_reference_and_preserves_counts: Generates randomized(min_x, min_y, w, h)camera viewports and proves both exact element-for-element equivalence with a reference iterator filter and the conservation lawvisible_pads + visible_traces + culled_count() == total_pads + total_traces.- Non-Zero Asymmetric Trace Geometry (
width_mm: 4.0): Assertingseg.bounds_mm() == Rect::from_min_max(pos2(8.0, 18.0), pos2(32.0, 26.0))catches any mutant that replaces* 0.5with/ 0.5or+ 0.5, or skips.expand(...)altogether. - Exact Budget Boundary (
500vs.501): Testingcheck_cull_budget(500, 500)(Ok(500)) alongsidecheck_cull_budget(501, 500)(Err(501)) kills the<=to<boundary mutant.
Complete Lesson 0020 Code Listing (In Context)
Here are the complete spatial viewport culling, telemetry, and test additions in crates/pcb-doc/src/lib.rs and src/main.rs in full context:
impl TraceSegment {
pub fn bounds_mm(&self) -> Rect {
Rect::from_two_pos(self.start, self.end).expand(self.width_mm * 0.5)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ViewportCullStats {
pub visible_pads: usize,
pub total_pads: usize,
pub visible_traces: usize,
pub total_traces: usize,
}
impl ViewportCullStats {
pub fn culled_count(&self) -> usize {
(self.total_pads - self.visible_pads) + (self.total_traces - self.visible_traces)
}
pub fn summary_badge(&self) -> String {
format!(
"Cull: {}/{} pads, {}/{} traces ({} skipped)",
self.visible_pads,
self.total_pads,
self.visible_traces,
self.total_traces,
self.culled_count()
)
}
}
pub fn check_cull_budget(measured_us: u64, target_max_us: u64) -> Result<u64, u64> {
if measured_us <= target_max_us {
Ok(measured_us)
} else {
Err(measured_us)
}
}
impl BoardModel {
pub fn culled_pads_in_rect(&self, viewport_mm: Rect) -> Vec<SmdPad> {
let mut out = Vec::with_capacity(self.pads.len());
for pad in self.pads.iter() {
if pad.bounds_mm().intersects(viewport_mm) {
out.push(*pad);
}
}
out
}
pub fn culled_traces_in_rect(&self, viewport_mm: Rect) -> Vec<TraceSegment> {
let mut out = Vec::with_capacity(self.traces.len());
for seg in self.traces.iter() {
if seg.bounds_mm().intersects(viewport_mm) {
out.push(*seg);
}
}
out
}
pub fn cull_stats_for_rect(&self, viewport_mm: Rect) -> ViewportCullStats {
ViewportCullStats {
visible_pads: self.culled_pads_in_rect(viewport_mm).len(),
total_pads: self.pads.len(),
visible_traces: self.culled_traces_in_rect(viewport_mm).len(),
total_traces: self.traces.len(),
}
}
}
impl PcbReviewerApp {
/// Computes active viewport culling telemetry based on `self.view.enable_viewport_culling`.
pub fn active_cull_stats(&self) -> ViewportCullStats {
if self.view.enable_viewport_culling {
self.board.cull_stats_for_rect(self.view.pcb_scene_rect)
} else {
ViewportCullStats {
visible_pads: self.board.pads.len(),
total_pads: self.board.pads.len(),
visible_traces: self.board.traces.len(),
total_traces: self.board.traces.len(),
}
}
}
}
// Inside `view_state_selection_focus_measurement_undo_coalescing_and_session_io_work` (`src/main.rs`):
app.view.pcb_scene_rect = Rect::from_min_max(pos2(12.0, 14.0), pos2(25.0, 24.0));
assert_eq!(app.active_cull_stats().visible_pads, 1);
assert_eq!(app.active_cull_stats().culled_count(), 6);
app.view.enable_viewport_culling = false;
assert_eq!(app.active_cull_stats().visible_pads, 3);
assert_eq!(app.active_cull_stats().culled_count(), 0);
// Inside `standalone_layer_swatch_and_headless_ui_workflows_pass` (`src/main.rs`):
assert!(
harness
.query_by_label("Cull: 3/3 pads, 6/6 traces (0 skipped)")
.is_some()
);
harness.get_by_label("Enable Viewport Culling").click();
harness.run();
assert!(!harness.state().view.enable_viewport_culling);
Rect::intersects) & Frame Budget
Rect::intersects • Vec::with_capacity • PerfBudget
Drag the camera X/Y and viewport width sliders below. Watch how culled_pads_in_rect and culled_traces_in_rect test each primitive's axis-aligned bounding box (bounds_mm().intersects(viewport_mm)) so off-screen geometry is skipped before Painter tessellation.
1. Camera Frustum (pcb_scene_rect)
2. ViewportCullStats & Budget
TraceSegment::bounds_mm & Rect::intersects Culling
Skill Practice • AABB Culling & PerfBudget
Complete TraceSegment::bounds_mm, BoardModel::culled_pads_in_rect, and check_cull_budget.
impl TraceSegment {
pub fn bounds_mm(&self) -> Rect {
Rect::from_two_pos(self.start, self.end).
}
}
pub fn culled_pads_in_rect(&self, viewport_mm: Rect) -> Vec<SmdPad> {
let mut out = Vec::with_capacity(self.pads.len());
for pad in self.pads.iter() {
if pad.bounds_mm(). {
out.push(*pad);
}
}
out
}
pub fn check_cull_budget(measured_us: u64, target_max_us: u64) -> Result<u64, u64> {
if measured_us {
Ok(measured_us)
} else {
Err(measured_us)
}
}
proptest, Budget Gate & Mutation Coverage (0020)
cargo test --workspace + proptest + cargo mutants
Run the workspace test suite (including prop_spatial_culling_matches_reference_and_preserves_counts) and mutate the spatial culling, statistics, and budget functions:
cargo test --manifest-path ./apps/0020-viewport-culling-and-cached-tessellation/Cargo.toml --workspace
cargo mutants --manifest-path ./apps/0020-viewport-culling-and-cached-tessellation/Cargo.toml --workspace --in-place -F "TraceSegment::bounds_mm|ViewportCullStats|culled_pads_in_rect|culled_traces_in_rect|cull_stats_for_rect|check_cull_budget|active_cull_stats"
Recommended Primary Source
emath::Rect::intersects & Rect::expand Official API Docs and std::vec::Vec::with_capacity Standard Library Docs.
std::sync::mpsc) & Live ProgressBar UI, where we offload session parsing onto a worker thread with staged progress reporting, clean cancellation, and ctx.request_repaint()!