Skip to content

Claude: Optimize SVG path rasterization (~10% faster)#586

Open
treeform wants to merge 1 commit into
masterfrom
claude-svg-bench
Open

Claude: Optimize SVG path rasterization (~10% faster)#586
treeform wants to merge 1 commit into
masterfrom
claude-svg-bench

Conversation

@treeform

Copy link
Copy Markdown
Owner

Summary

Speeds up the tests/bench_svg.nim Ghostscript Tiger render from 6.89 ms to 6.16 ms min (~10.5%, measured with interleaved A/B runs against a master-built binary on an M-series Mac, -d:danger).

All changes are in src/pixie/paths.nim and keep output effectively identical — every test_svg/test_paths xray diff score matches master to 4 decimal places (remaining differences come from replacing divides with multiplies by the reciprocal, well below the masters' own tolerance).

Changes

  • Delta + prefix-sum coverage accumulation. Instead of adding sample coverage to every covered pixel for each of the 5 AA subsample lines, each fill span now writes 2 entries into an int16 delta buffer. One prefix-sum pass per scanline (NEON-vectorized on arm64) converts deltas to the exact same coverage bytes. Long interior spans go from 5 passes over every pixel to O(1) writes + 1 pass.
  • Single-pass hit gathering. computeCoverage previously rescanned all active entries once per subsample line (5x). Entries are now loaded once, their fields kept in registers, and hits bucketed per subsample line — same hit order, bit-identical sort/walk behavior. On the tiger this cuts 467k entry visits per render to 93k.
  • Per-scanline coverage regions. The covered spans are tracked while walking hits, so blending only touches real coverage (small gaps fused) instead of the full path-bounds width, and the per-scanline zeroMem of the coverage buffer is gone (deltas are provably re-zeroed by the prefix pass).
  • PartitionEntry stores 1/m, turning the per-hit divide in solveX/hit gathering into a multiply.
  • Partition clipping via the precomputed slope instead of generic segment x line intersects().
  • Thread-local scratch reuse. fillShapes and partitionSegments reuse grow-only thread-local buffers (coverages, deltas, hits, buckets, partitions + entries, segments), removing a few thousand allocations per tiger render. Partitions gained a numEntries count so entry storage can be reused without reallocating.
  • Blend micro-opts. The blender() proc-pointer lookup is hoisted out of the trapezoid pair loop and blendNormal is called directly for the common NormalBlend case in the edge-coverage loops.

Testing

  • test_svg, test_paths, test_images_draw, test_paints, test_contexts, test_fonts all pass; xray scores unchanged vs master.
  • Benchmarked with interleaved master/branch runs to control for machine load.

🤖 Generated with Claude Code

- computeCoverage: gather subsample hits in one pass over entries
  (each entry loaded once instead of once per subsample line)
- Accumulate coverage as int16 deltas and convert with a single
  prefix-sum pass (NEON accelerated) instead of adding sample
  coverage to every covered pixel per subsample
- Track covered regions per scanline so blending and clearing only
  touch real coverage instead of the full path width
- Store 1/m in PartitionEntry, replacing divides with multiplies
- Reuse thread-local scratch buffers across fillShapes calls and
  inside partitionSegments, removing per-path allocations
- Clip partition entries with the precomputed slope instead of
  generic segment-line intersection
- Hoist blender lookup out of the trapezoid loop and inline
  blendNormal for the common blend mode

Ghostscript Tiger render: 6.89 -> 6.16 ms min (same machine,
interleaved runs), test_svg/test_paths/test_images_draw/test_paints/
test_contexts/test_fonts all pass with unchanged diff scores.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@treeform treeform changed the title Optimize SVG path rasterization (~10% faster) Claude: Optimize SVG path rasterization (~10% faster) Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant