CultMath design.md, README, notices: the GLSL target

design.md gains "GLSL Target: Source Transformations" (the list, the common-subset rule, the
regeneration command, the derived-file rule, the two parity legs). README says how a WebGL
consumer includes CultMath.glsl. THIRD-PARTY-NOTICES lists CultMath.glsl as MPL-2.0, because
it inlines the Phacelle include.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-02 20:09:45 +02:00
commit 5ae207e82b
3 changed files with 68 additions and 2 deletions

View file

@ -41,6 +41,12 @@ shader semantics.
ball. Every one returns an interval containing the function's value at every
point of its input, so a raymarch can skip a segment whose `hi` is below its
cutoff; `cultmath_iv_*` in HLSL. See `docs/design.md`, "Intervals".
- `shaders/CultMath.glsl`, the same `cultmath_*` library in GLSL ES 3.00 for
WebGL2, generated from `CultMath.hlsl` (never edited by hand). GLSL has no
`#include`: a WebGL consumer concatenates the file's text after its own
`#version 300 es` line and `precision highp float; precision highp int;`
(the library declares no precision, and its hashes need 32-bit integers),
then appends its own shader source.
- `Voronoi.SampleTones`, a C# batch surface that calls the Rust
`cultmath-core` native kernel when `cultmath_core` is available and falls back
to the managed parity path otherwise.

View file

@ -35,7 +35,9 @@ gradient and the exact pruning are new here.
- License: MPL-2.0 (<https://mozilla.org/MPL/2.0/>). MPL-2.0 is file-level, so it
covers exactly the files that carry the port and nothing else:
`src/CultMath/math.Phacelle.cs`, `src/CultMath/CultPhasor.cs`,
`shaders/CultMath.Phacelle.hlsl` and the Unity copy of that include. Each
carries the MPL-2.0 header and this attribution. The rest of CultMath is MIT,
`shaders/CultMath.Phacelle.hlsl` and the Unity copy of that include, and
`shaders/CultMath.glsl`, which is generated from the HLSL with that include
inlined, so the generated file as a whole is MPL-2.0. Each carries the
MPL-2.0 header and this attribution. The rest of CultMath is MIT,
so the `GameCult.Math` NuGet package and the `org.gamecult.cultmath` Unity
package both declare `MIT AND MPL-2.0` and ship this file.

View file

@ -133,6 +133,64 @@ as its value. `CultCellular` (`math.cs`, `cellular`) and `CultPhasor`
retyped field does not get a second comparison path or a shape of its own;
it fails the same walk that already handles a bare vector return.
### GLSL Target: Source Transformations
`shaders/CultMath.glsl` is a GLSL ES 3.00 (WebGL2) library generated from
`shaders/CultMath.hlsl` by `GlslLowering` (in the test project). It is a derived
file: never edit it by hand. Regenerate it, and the golden fixture
`tests/CultMath.Tests/fixtures/glsl-parity.json`, with:
```powershell
$env:CULTMATH_WRITE_GLSL = "1"; dotnet test packages/cultmath/tests/CultMath.Tests --filter GlslMirrorTests
```
`GlslMirrorTests.CommittedGlslEqualsLowering` fails on any difference between the
committed file and the lowering of the committed HLSL. These transformations,
in order, are the complete list:
1. Each `#include "name"` is replaced in place by the included file, by the
same resolver the C# mirror uses; GLSL has no `#include`.
2. Include guards are dropped, and so are the functions taking
`Texture2D`/`SamplerState`; the host shader samples its own textures.
3. Type names: `floatN`, `intN`, `uintN` and `boolN` become `vecN`, `ivecN`,
`uvecN` and `bvecN`.
4. File-scope `static const` becomes `const`.
5. C-style casts `(float)x`, `(int)x` and `(uint)x` become `float(x)`, `int(x)`
and `uint(x)`. The operand is found by a balanced-bracket scanner:
`(int)((state >> 28) + 4u)` nests parentheses.
6. Intrinsics are renamed: `lerp` to `mix`, `frac` to `fract`, `rsqrt` to
`inversesqrt`, `asuint` to `floatBitsToUint`, `asfloat` to
`uintBitsToFloat`, and `saturate(x)` becomes `clamp(x, 0.0, 1.0)`.
7. Float literals pass through unchanged.
8. The library is wrapped in a `CULTMATH_GLSL` guard.
9. The first line names the file as generated and says how to regenerate it.
GLSL ES 3.00 is stricter than HLSL: no implicit conversions between int, uint
and float, no C-style casts, and no `step(genType, float)` overload. Those gaps
are closed in the HLSL, never in the lowering: the HLSL is written in the subset
both languages share, with edits that are legal HLSL and compute the same bits
(`step(h, float4(0.0, 0.0, 0.0, 0.0))` rather than `step(h, 0.0)`, `+ 4u` where
a uint is added, `(float)((uint)hash.x >> 8)` before a float multiply). The C#
mirror and the FXC and dxc compiles keep those edits honest. A body that needs a
transformation outside the list fails `GlslMirrorTests.NoHlslOnlyTokensSurvive`
rather than gaining a special case.
The library declares no precision. The host shader does, and it must be
`precision highp float; precision highp int;`: the PCG hashes need 32-bit
integers. `tools/compile-glsl.ps1` writes such a wrapper (its own `#version
300 es` line, the precision, the library by string concatenation, and one call
of every public function) and compiles it with glslang, which
`tools/get-glslang.ps1` fetches pinned. The Unity package does not ship the
GLSL file.
Parity runs in two legs, each with one owner: HLSL against C# by the mirror
test, bit for bit; GLSL against C# by the golden fixture, which records 256
seeded cases per function family as float32 bit patterns, evaluated by the
consumer on WebGL2 and read back. Integer-only families (`pcg3d`, `pcg4d`) must
match exactly; float families are ulp-bounded, the bound measured on the device.
`iv_exp` is not in the fixture, because its C# bits come from the platform's
`exp`.
## Invariant 8: Value-and-Gradient Primitives
A primitive tagged invariant 8 returns its value and analytic gradient