mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Add opaque RGB light color and up to ten independently adjustable lights to
Revolve, Extrude & Bevel, Inflate and Rotate. Keep light selection, Add/Delete,
selected-light gizmos and preview transactions on the existing command path.
Preserve legacy white-light Inflate polygons, paint composition and seam joins,
including the 0.05 seam minimum. Use shared smooth normals for colored Inflate,
and batch solid-fill RGB channel paths behind one surface clip. Keep source
paths editable and SVG/PDF output vector artwork.
Rebase onto main f7de3d6a, retaining both sides of progress docs and all 22
catalogs. Shade freeform points once through the shared paint mapper. Document
release measurements and a reproducible CLI fixture, including the remaining
source-gradient cost and the one-light timing tradeoff.
Validation: full cargo xtask ci passed all 10 steps; 7,978 tests passed, zero
failed and 11 ignored. Four legacy white-light PNGs match unchanged main byte
for byte. Three paired release bench/perf repetitions completed without busy
warnings; all six standard perf runs passed every budget. Two-light solid
Inflate improved from 136.2 to 96.8 ms/frame at 1200x1200, default eight threads.
76 lines
6.5 KiB
Markdown
76 lines
6.5 KiB
Markdown
# 3D lighting performance
|
||
|
||
Measured 2026-10-17 for PR #2706 on one macOS machine.
|
||
|
||
## Method
|
||
|
||
- Apple M1 Pro, 10 cores, 16 GiB RAM, macOS 26.5.1 (arm64).
|
||
- `cargo build --release -p vectorcraft-cli`, the workspace release profile (thin LTO, one codegen unit), without bundled fonts.
|
||
- Before: published PR head `f63fafdb`. After: the reviewed implementation rebased on main `f7de3d6a`, with legacy white-light Inflate restored and colored solid-fill channel groups batched per surface. The main update is included in this comparison.
|
||
- Both executables were retained separately. No compilation ran during measurement. Three paired repetitions alternate before/after for each fixture. Each `bench` invocation warms its renderer once, then averages five frames for each thread setting. The table reports the median of the three reported averages.
|
||
- `bench` renders a 400 x 400 pt artboard at 1200 x 1200 pixels, fitting it to 95% of the viewport. The scene contains one white-filled ellipse, centered at (200, 200), with radii 110 and 140 pt, no stroke, Inflate volume 50%, depth 0, solid cap, one side, view rotation (-20, 25, 0), no perspective, and 15% neutral ambient.
|
||
- Two lights: orange `#ff8040`, intensity 70%, rotation 135 degrees, height 35 degrees, softness 15%; blue `#4080ff`, intensity 65%, rotation -35 degrees, height 25 degrees, softness 10%. Both are in front.
|
||
- The one-light case uses the legacy fields: `#ff8040`, intensity 80%, rotation 125.26438968275465 degrees, height 30 degrees, softness 0%. The white case changes its color to `#ffffff`.
|
||
- The source-gradient case uses the same two lights with a linear source gradient from (90, 200) to (310, 200), orange at opacity 0.45 and blue at opacity 0.7.
|
||
- `perf` covers the standard 50,000-path synthetic budget scene. It was also run three times per executable, alternating before/after. Its table reports medians.
|
||
|
||
## Reproduce
|
||
|
||
Create the two-light scene using the updated CLI, then use the same saved file with both executables:
|
||
|
||
```sh
|
||
vectorcraft-cli run \
|
||
--cmd file.new --params '{"width":400,"height":400}' \
|
||
--cmd document.setup --params '{"backgroundContents":"white"}' \
|
||
--cmd path.create --params '{"d":"M310.0 200.0C310.0 277.3198649763111 260.7513224813873 340.0 200.0 340.0C139.2486775186127 340.0 90.0 277.3198649763111 90.0 200.0C90.0 122.6801350236889 139.2486775186127 60.0 200.0 60.0C260.7513224813873 60.0 310.0 122.6801350236889 310.0 200.0Z"}' \
|
||
--cmd paint.setFill --params '{"color":"#ffffff"}' \
|
||
--cmd paint.setStroke --params '{"none":true}' \
|
||
--cmd effect.apply --params '{"effect":"threeD.inflate","item":null,"params":{"rotationX":-20,"rotationY":25,"rotationZ":0,"ambient":15,"volume":50,"depth":0,"cap":true,"bothSides":false,"lights":[{"color":"#ff8040","intensity":70,"rotation":135,"height":35,"softness":15},{"color":"#4080ff","intensity":65,"rotation":-35,"height":25,"softness":10}]}}' \
|
||
--cmd document.save --params '{"path":"/tmp/inflate-two-lights.vectorcraft"}'
|
||
|
||
vectorcraft-cli bench /tmp/inflate-two-lights.vectorcraft --size 1200x1200 --iters 5
|
||
vectorcraft-cli perf
|
||
```
|
||
|
||
## Measurements
|
||
|
||
Milliseconds per frame, medians of three five-frame averages. `8` is the CLI default thread count on this machine; `0` selects the single-thread renderer.
|
||
|
||
| Inflate scene | Before, 8 | After, 8 | Before, 0 | After, 0 |
|
||
|---|---:|---:|---:|---:|
|
||
| Two colored lights, solid fill | 136.2 | 96.8 | 147.2 | 100.6 |
|
||
| One colored light, solid fill | 94.4 | 96.7 | 107.0 | 100.0 |
|
||
| Legacy white light, restored polygon shading | 12.3 | 4.1 | 27.3 | 9.5 |
|
||
| Two colored lights, transparent source gradient | 145.6 | 146.8 | 156.9 | 157.0 |
|
||
|
||
The two-light solid-fill case takes 28.9% less frame time at the default thread count and 31.7% less in the single-thread renderer. The one-light case is 2.4% higher with eight threads and 6.5% lower single-threaded. The source-gradient case retains its previous per-facet compositor and changes by less than 1% in these medians. Restoring legacy white-light polygons also removes the extra smooth triangles from that path.
|
||
|
||
All six `perf` runs exited successfully, with no busy-machine warning and every individual budget row passing. The reported one-minute load averages were 4.5 to 5.9 on ten cores. Median milliseconds:
|
||
|
||
| Standard 50,000-path workload | Before | After | Budget |
|
||
|---|---:|---:|---:|
|
||
| render: fit page, 2880×1800 | 47.15 | 47.80 | 100 |
|
||
| render: background refresh after a pan | 48.18 | 46.71 | 100 |
|
||
| render: 400% zoom, 2880×1800 | 13.45 | 13.68 | 16 |
|
||
| hit test (per click) | 0.43 | 0.42 | 2 |
|
||
| save .vectorcraft | 52.78 | 52.15 | 300 |
|
||
| open .vectorcraft | 143.84 | 142.55 | 300 |
|
||
| export SVG | 207.37 | 206.25 | 500 |
|
||
| Pathfinder Unite, 1,000 paths | 43.18 | 43.29 | 150 |
|
||
| handle drag, 5,000-anchor path (per move) | 0.94 | 0.84 | 8 |
|
||
| segment drag, 5,000-anchor path (per move) | 0.87 | 0.92 | 8 |
|
||
|
||
The colored fixture uses the same projected mesh before and after. The solid-fill batch reduces compositor groups and clips; it retains three sets of channel facet paths. Smooth vector export still has many gradient elements.
|
||
|
||
## Rendering structure
|
||
|
||
For a smooth colored solid fill, all facets share one compound coverage clip and three isolated channel groups. Each channel has an opaque black backdrop; Screen combines the disjoint RGB channels and Multiply applies the source color. Face order is retained in each channel. Surface opacity and outer coverage apply once. The group/clip counts are independent of triangle count, and export remains vector artwork.
|
||
|
||
Source gradients retain their independently placed per-facet vector compositor. They remain a more expensive case. Patterns retain their neutral shading approximation. White-light Inflate uses the original polygon shading, paint composition and default seam joins; the seam width retains its 0.05 to 0.75 pt bounds.
|
||
|
||
Geometry/effects tests cover legacy polygons and seam bounds, shared smooth normals, finite ramps, holes and constant group/clip counts. Engine tests cover the batched SVG round trip, vector PDF, expansion, undo, compound holes and surface opacity.
|
||
|
||
## Legacy white-light comparison
|
||
|
||
Four original synthetic scenes were exported at 1200 x 1200 from unchanged main `f7de3d6a` and the updated implementation. The PNG files match byte for byte in all four cases: the legacy white-lit ellipse above; the same ellipse with both sides, hollow cap, twist 60 degrees, taper 70%, perspective 80 degrees and view Y rotation 145 degrees; an unshaded ellipse; and the transparent source gradient above under the legacy white light. The command recipe was also checked against the measured colored fixture's image pixels.
|