Files
OneCAD-Draft/WIRE_OPTIMIZATION_PLAN.md
T
HakanandClaude Opus 4.7 d3986e9cba docs(perf): add wire pipeline cold-open optimization plan
Documents the 4-step plan to reduce first-file-open latency by attacking
wire vertex upload volume: mapped_at_creation, Rayon parallel emit,
instanced rendering (the big lever, ~6.5x memory cut), and per-instance
packing. Each step lands as its own commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 09:45:57 +03:00

81 lines
3.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Wire Pipeline — Cold-Open Optimization Plan
Goal: reduce first-file-open latency (tessellate → upload → GPU buffer
creation) for wire entities. Steady-state draw cost is already cheap; the
bottleneck on cold open is **upload volume** and **vertex emission**.
## Baseline
Each wire segment currently produces **6 vertices × 96 B = 576 B**. The same
`pos_a / pos_b / color / half_width / pattern_length / pat0 / pat1` values
are duplicated across all 6 corners of the quad. For a drawing with 1 M
segments this is **~576 MB** of CPU-side `Vec<WireVertex>` plus the same
amount uploaded over PCIe.
Build path: `WireGpu::from_batch` (single-threaded loop) → `Vec<WireVertex>`
→ `create_buffer_init` (one alloc + one memcpy into a staging buffer the
driver then DMAs to VRAM).
## Step 1 — `mapped_at_creation` upload
Replace `create_buffer_init(contents)` with `create_buffer(mapped: true)` +
write the vertex stream directly into the mapped slice. Skips the
intermediate `Vec<WireVertex>` → staging-buffer memcpy.
- **Files**: `src/scene/pipeline/wire_gpu.rs`
- **Risk**: low (size known ahead of time; no semantic change).
- **Expected win**: 1 alloc + 1 memcpy per buffer dropped. Modest but free.
## Step 2 — Rayon parallel vertex emission
`from_batch` walks wires serially. Each wire's vertex generation is
independent (no shared state, no order dependency — wires are already
keyed by style upstream in `block_cache`). Use `rayon::par_iter().flat_map`
to emit per-wire vertex slices in parallel, then concatenate.
- **Files**: `src/scene/pipeline/wire_gpu.rs`
- **Risk**: low. Output ordering does not matter for correctness.
- **Expected win**: N-core speedup on multi-wire drawings; meaningful on
files with many short wires (typical CAD geometry).
## Step 3 — Instanced rendering (the big win)
Replace 6-vertex quad expansion with **1 instance per segment** + 4-vertex
unit quad shared across all instances. Per-instance struct (~88 B) holds:
`pos_a, pos_b, color, distance_a, distance_b, half_width, pattern_length,
pat0, pat1`. `which_end` and `side` are derived from `@builtin(vertex_index)`
inside the vertex shader.
- **Files**: `src/scene/pipeline/wire_gpu.rs`, `src/shaders/wire.wgsl`,
`src/scene/pipeline/mod.rs` (pipeline `VertexBufferLayout`).
- **Risk**: medium. Two vertex buffers (per-vertex + per-instance), shader
derives `which_end` / `side` from `vertex_index`. `WireGpu::from_batch`
emits 1 instance per segment instead of 6 verts.
- **Expected win**: **~6.5× less GPU memory & upload bandwidth per
segment** (576 B → 88 B). Biggest cold-open lever.
## Step 4 — Tighter per-instance packing
After Step 3 the instance struct is the new hot path. Pack:
- `color: [f32; 4]` → `[u8; 4]` (`Unorm8x4`): 16 B → 4 B, no visible
quality loss at 8-bit display.
- `which_end / side` already eliminated by Step 3.
- `pat0 / pat1` stay (encoder relies on up to 8 dash elements).
Net: ~88 B → ~76 B per instance. ~15 % further reduction on top of Step 3.
- **Files**: `src/scene/pipeline/wire_gpu.rs`, `src/shaders/wire.wgsl`.
- **Risk**: low (vertex-attribute format change only).
- **Expected win**: ~15 % less upload after Step 3.
## Ordering rationale
1-2 first — independent, low risk, immediate wins.
3 next — restructures the pipeline; depends on no prior changes but is the
single biggest lever.
4 last — only pays off after Step 3 turns the per-instance struct into the
hot path.
Each step ends with a `cargo check` (warnings-free) and a commit.