|
|
Donner 0.8.0-pre
Embeddable browser-grade SVG2 engine
|
Status: Developer reference. Describes Geode's as-built implementation of the Slug algorithm (per-pixel ray/Bézier-crossing winding) — the pipeline a contributor must understand to work on the fill/coverage shaders or the path encoder — plus the invariants it relies on and the known limitations (D3/D4/D6) that remain future work. The per-pixel AA coverage math is documented in 0041; this doc covers the rest of the pipeline.
Related: 0041 anti-aliasing, 0038 text parity, 0021
Geode implements the Slug algorithm clean-room (ISC). The algorithmic references are:
The patent (US10373352B1) was dedicated to the public domain on 2026-03-17; reference shaders are MIT. Geode's implementation is independent.
Geode renders a Path by encoding it into band-binned Y-monotonic quadratic curves on the CPU, then per-pixel casting a horizontal ray and counting winding from the curves in the pixel's band. Two halves:
GeodePathEncoder::encode lowers a Path into an EncodedPath of bands + quadratic curves for the GPU:
Per pixel, fs_main casts a horizontal ray and counts winding from the band's curves:
All six Slug shaders (slug_fill, slug_gradient, slug_mask, and the three vendor-gated *_alpha_coverage variants) run the same integer-winding curve_winding tracer and the same solve_quadratic.
Severity: Critical = correctness/parity bug, broad blast radius; Moderate = narrower or conditional; Benign = legitimate adaptation or already-correct. Most are resolved (implementation notes / invariants in §1); the open ones are the known limitations in §3.
| # | Sev | Area | Geode location | Status |
|---|---|---|---|---|
| D1 | Critical | solve_quadratic numerical stability | all six Slug shaders | resolved — Citardauq form (§1.2 step 3) |
| D2 | Critical (latent) | encoder must not silently flatten cubics | GeodePathEncoder.cc:185 | resolved — hard-fail assert documents the cubic-free invariant (§1.1) |
| D3 | Critical | pixels-per-em scale ignores rotation/shear | slug_fill.wgsl:393 | open / known limitation (§3) |
| D4 | Moderate | band binning over-includes curves; no per-band sort | GeodePathEncoder.cc:253 | open (perf only; correctness benign) |
| D5 | Moderate | even-odd fill rule | slug_fill.wgsl | resolved/moot — integer parity (winding & 1), never a fractional fold |
| D6 | Moderate | absolute degeneracy threshold + missing b≈0 NaN guard | slug_fill.wgsl:233 | open / known limitation (§3) |
| D7 | — | (0x2E74 classification) | — | moot — not present on the clean tree; committed shaders use one-directional curve_winding, no classification tracer (a mid-edit-artifact reading, like D5) |
| D8 | Benign | orthographic-2D half-pixel dilation | slug_fill.wgsl:182 | no action — correct for affine 2D |
| D9 | Benign | lines as degenerate quads (control pt = midpoint) | GeodePathEncoder.cc:147 | no action — standard Slug |
| D10 | Benign | implicit-close of open subpaths | GeodePathEncoder.cc:116 | no action — SVG-fill conformance |
| D11 | Benign | ~32px/band, max 256 | GeodePathEncoder.cc:37 | no action — impl-defined granularity |
| D12 | Benign | f32 SSBO curve storage vs reference f16 textures | slug_fill.wgsl:91 | no action — more precise WebGPU adaptation |
D1 — solve_quadratic stability. All six shaders previously used the naive (-b ± √disc)/2a, which loses precision to subtractive cancellation when b and a root share a sign — worst exactly at the tangent/grazing crossings the winding count is most sensitive to. Replaced uniformly with the Citardauq form (§1.2 step 3). Output-neutral on every passing test (strict-identity geode goldens, all three resvg parity modes, geode unit/encoder/shader tests, CPU lanes unchanged); a robustness improvement for grazing crossings, flips no gates.
D2 — cubic→chord landmine. extractCurves's CurveTo case used to discard both cubic control points and emit a quad with control point = endpoint midpoint = a straight chord — silently wrong geometry. Dead in current call paths (encode runs cubicToQuadratic first), but a future caller handing a raw cubic in would have silently flattened every cubic. Replaced with UTILS_RELEASE_ASSERT_MSG(false, …) documenting the cubic-free invariant — hard-fail instead of silent corruption. Unreachable today, so output-neutral.
D5 — even-odd fill rule. An earlier audit recorded a triangle-wave fold of fractional coverage for even-odd, but that was read mid-edit during the reverted analytical-AA experiment (0041). The committed slug_fill.wgsl has no apply_fill_rule and no fractional fold: even-odd is integer crossing parity ((winding & 1) != 0) in sample_is_inside, evaluated per sub-pixel sample on the 4× MSAA sample_mask path — identical across all shaders. Geode has a single, spec-correct even-odd behavior. Invariant: if the analytical-AA coverage rework is ever revisited, it must preserve integer-parity even-odd.
These are genuine, still-open divergences from the published method. They are scoped to the analytical-AA coverage rework (which is shelved — 0041), because they only change observable output once a fractional-coverage tracer lands; on the current integer-winding sample_mask path they do not affect the accepted edge floor.
The original ledger (opened 2026-05-25) was an investigation tracker. Two entries (D1's "fourth analytical tracer" framing, D5's triangle-wave fold) were mid-edit artifacts — the audit read slug_fill.wgsl while the 0041 analytical-AA experiment was in flight (since reverted). On the clean committed tree all six shaders run the same integer-winding tracer and the same solve_quadratic; there is no 0x2E74 analytical-classification tracer anywhere (that remains 0039 future work), and even-odd is integer parity everywhere. D1 (real shared item: cancellation-prone solve_quadratic) and D2 were then closed as hygiene fixes; D5 was re-grounded as moot; D3/D4/D6 carry forward as §3.