From 9b6d5437fa89930eae9571786cfabf1cbc21e0d4 Mon Sep 17 00:00:00 2001 From: Hakan Date: Wed, 13 May 2026 10:11:54 +0300 Subject: [PATCH] revert: drop wire segment caps that silently truncated geometry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove MAX_SEGS_PER_WIRE = 100_000 and MAX_TOTAL_SEGS = 3_000_000. These were stop-gap GPU-memory guards that mutated visible output: single complex entities were cut mid-geometry, and entire wires past the 3M total were dropped by arrival order with no spatial awareness. Either is worse than honestly drawing whatever the file contains — truncated arcs and missing road segments look like bugs to the user, not "we ran out of GPU memory." The matching "Temporary Workarounds" section in RENDERING_OPTIMIZATION.md goes with them. Per-entity LOD and viewport culling (Phase 1 + 3.2 in the same doc) remain the right long-term answer; until they land, prefer correctness over a soft crash on pathological files. Co-Authored-By: Claude Opus 4.7 --- RENDERING_OPTIMIZATION.md | 44 ---------------------------------- src/scene/pipeline/mod.rs | 31 +----------------------- src/scene/pipeline/wire_gpu.rs | 22 ++--------------- 3 files changed, 3 insertions(+), 94 deletions(-) diff --git a/RENDERING_OPTIMIZATION.md b/RENDERING_OPTIMIZATION.md index 51dd1bb02..d7974ada4 100644 --- a/RENDERING_OPTIMIZATION.md +++ b/RENDERING_OPTIMIZATION.md @@ -12,50 +12,6 @@ Current pipeline order: --- -## Temporary Workarounds (to be reverted) - -> **These are stop-gap measures to prevent crashes on large files. They must be removed -> once the proper Phase 1–3 optimizations are in place, because they silently drop geometry -> rather than culling it intelligently.** - -### Wire Segment Hard Caps (commit 321433e) - -**Problem:** Some entities — particularly dense `PolyfaceMesh` / `PolygonMesh` edge meshes, -high-resolution splines, or very large polylines — tessellate into hundreds of thousands of -line segments. Each segment expands to 6 GPU vertices × 96 bytes = 576 bytes. A single -pathological entity could allocate hundreds of megabytes; a large file with many such entities -exhausts all available GPU memory and crashes the renderer. - -**Temporary fix applied in `src/scene/pipeline/wire_gpu.rs` and `src/scene/pipeline/mod.rs`:** - -1. **Per-entity cap — `MAX_SEGS_PER_WIRE = 100_000`** - Defined as a `pub const` in `wire_gpu.rs`. Applied in both `WireGpu::build()` (used by - `WireGpu::new`) and `WireGpu::from_batch()`. If a single `WireModel` contains more than - 100 K segments, the excess tail is silently truncated and a warning is printed to stderr: - ``` - [H7CAD] wire '': segments exceeds limit (100000), truncating - ``` - -2. **Scene-level budget — `MAX_TOTAL_SEGS = 3_000_000`** - Enforced in `Pipeline::upload_wires()`. Wires are processed in the order they arrive; once - the running segment total would exceed 3 M, all remaining wires are skipped for that frame: - ``` - [H7CAD] upload_wires: skipped wire(s) — total segment budget (3000000) exceeded - ``` - At 576 bytes/segment the budget caps GPU wire memory at roughly **1.6 GB**. - -**Why this must be reverted:** -- Truncation cuts an entity mid-geometry; the rendered result is visually wrong. -- Skipping by arrival order is arbitrary — important visible entities may be dropped while - invisible off-screen entities are retained (no spatial awareness). -- The correct solution is Phase 1 (AABB cull before upload) + Phase 3.2 (adaptive arc segments), - which eliminate the excess geometry without ever creating it in the first place. - -**Revert when:** Phase 1.3 (CPU cull before upload) is landed and verified to keep peak GPU -wire memory below ~512 MB for the pathological files that triggered these caps. - ---- - ## Phase 1 — Viewport Bounding-Box Culling **Goal:** Skip CPU upload and draw calls for entities outside the camera view. diff --git a/src/scene/pipeline/mod.rs b/src/scene/pipeline/mod.rs index 37a479228..fb9b3a722 100644 --- a/src/scene/pipeline/mod.rs +++ b/src/scene/pipeline/mod.rs @@ -600,36 +600,7 @@ impl Pipeline { } pub fn upload_wires(&mut self, device: &wgpu::Device, wires: &[WireModel]) { - // Total segment budget: prevents a large file from exhausting GPU memory. - // Each segment → 6 GPU vertices × 96 bytes = 576 bytes; 3M segs ≈ 1.6 GB. - const MAX_TOTAL_SEGS: usize = 3_000_000; - let mut total_segs = 0usize; - let mut skipped = 0usize; - - self.gpu_wires = wires - .iter() - .filter(|w| { - let segs = w - .points - .len() - .saturating_sub(1) - .min(wire_gpu::MAX_SEGS_PER_WIRE); - if total_segs + segs > MAX_TOTAL_SEGS { - skipped += 1; - false - } else { - total_segs += segs; - true - } - }) - .map(|w| WireGpu::new(device, w)) - .collect(); - - if skipped > 0 { - eprintln!( - "[H7CAD] upload_wires: skipped {skipped} wire(s) — total segment budget ({MAX_TOTAL_SEGS}) exceeded" - ); - } + self.gpu_wires = wires.iter().map(|w| WireGpu::new(device, w)).collect(); // Full-brightness copies of selected wires, drawn on top of everything // in the selection overlay pass so they're always visible. diff --git a/src/scene/pipeline/wire_gpu.rs b/src/scene/pipeline/wire_gpu.rs index 6f88edff9..9be5ffe75 100644 --- a/src/scene/pipeline/wire_gpu.rs +++ b/src/scene/pipeline/wire_gpu.rs @@ -27,10 +27,6 @@ use crate::scene::wire_model::WireModel; use iced::wgpu; use iced::wgpu::util::DeviceExt; -/// Hard cap on segments per wire entity. Entities with more segments are -/// silently truncated to prevent a single complex object from exhausting GPU memory. -pub const MAX_SEGS_PER_WIRE: usize = 100_000; - // ── Vertex layout ───────────────────────────────────────────────────────── #[repr(C)] @@ -160,14 +156,7 @@ impl WireGpu { ]; let half_width = wire.line_weight_px * 0.5; let n = wire.points.len(); - let raw_segs = n.saturating_sub(1); - let capped_segs = raw_segs.min(MAX_SEGS_PER_WIRE); - if raw_segs > MAX_SEGS_PER_WIRE { - eprintln!( - "[H7CAD] wire '{}': {} segments exceeds limit ({MAX_SEGS_PER_WIRE}), truncating", - wire.name, raw_segs - ); - } + let capped_segs = n.saturating_sub(1); // Cumulative arc-length per point. let pts_len = capped_segs + 1; @@ -270,14 +259,7 @@ impl WireGpu { let half_width = wire.line_weight_px * 0.5; let n = wire.points.len(); - let raw_seg_count = if n >= 2 { n - 1 } else { 0 }; - let seg_count = raw_seg_count.min(MAX_SEGS_PER_WIRE); - if raw_seg_count > MAX_SEGS_PER_WIRE { - eprintln!( - "[H7CAD] wire '{}': {} segments exceeds limit ({MAX_SEGS_PER_WIRE}), truncating", - wire.name, raw_seg_count - ); - } + let seg_count = if n >= 2 { n - 1 } else { 0 }; let mut vertices: Vec = Vec::with_capacity(seg_count * 6); // Precompute cumulative arc-length at each point.