NC Stylo · Docs

3D lighting performance

Measured 2026-10-17 for PR #2706 on one macOS machine.

Method

Reproduce

Create the two-light scene using the updated CLI, then use the same saved file with both executables:

stylo-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.stylo"}'

stylo-cli bench /tmp/inflate-two-lights.stylo --size 1200x1200 --iters 5
stylo-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 .stylo 52.78 52.15 300
open .stylo 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.