Merge hands/cultmath-shader-lattice-loop

CultMath shaders: cultmath_power_border, cultmath_lattice_cell_distance and
cultmath_weighted_lattice_cells over a fixed-capacity site array
(CULTMATH_MAX_SITES, overridable). Spec cultmath-tapes:cut_spec:cut-shader-lattice-loop.r1,
report h1, Soul s1 holds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-17 17:20:23 +02:00
commit 86cd84a315
15 changed files with 1548 additions and 457 deletions

View file

@ -38,8 +38,9 @@ shader semantics.
drifting lattice whose cell weights come from the exact power border of the body span at each cell centre, with a stretched drifting lattice whose cell weights come from the exact power border of the body span at each cell centre, with a stretched
metric and an exact ring search over the stated range (`LatticeMaxBoost`, `LatticeMaxStretch`, `LatticeMaxSmoothing`). It composes the metric and an exact ring search over the stated range (`LatticeMaxBoost`, `LatticeMaxStretch`, `LatticeMaxSmoothing`). It composes the
public steps `lattice_site`, `lattice_weight`, `lattice_distance` and `lattice_smooth_step`. The shaders carry the four steps as public steps `lattice_site`, `lattice_weight`, `lattice_distance` and `lattice_smooth_step`. The shaders carry the four steps as
`cultmath_lattice_site`, `cultmath_lattice_weight`, `cultmath_lattice_distance` and `cultmath_lattice_smooth_step`; the ring and box loops `cultmath_lattice_site`, `cultmath_lattice_weight`, `cultmath_lattice_distance` and `cultmath_lattice_smooth_step`, and the whole function as `cultmath_weighted_lattice_cells(p, time, bodies,
are the shader caller's, written as the comment above them in `CultMath.hlsl` gives them, and the argument checks become its preconditions. count, ...)` over a `float4 bodies[CULTMATH_MAX_SITES]` array (128 by default; define the macro before including to change it);
`cultmath_power_border(p, sites, count)` is the matching border. The argument checks become the shader caller's preconditions.
- `shaders/CultMath.hlsl`, a canonical HLSL mirror include for shader-side - `shaders/CultMath.hlsl`, a canonical HLSL mirror include for shader-side
parity. HLSL already owns `float2`, `float3`, and `float4`; the include parity. HLSL already owns `float2`, `float3`, and `float4`; the include
exposes `cultmath_*` functions for shared semantics HLSL does not provide. exposes `cultmath_*` functions for shared semantics HLSL does not provide.

View file

@ -95,7 +95,8 @@ and HLSL's one-based `_11`..`_33` element names.
Roslyn against CultMath. These transformations are the complete list; if a body Roslyn against CultMath. These transformations are the complete list; if a body
needs anything else, the test fails: needs anything else, the test fails:
1. Include guards (`#ifndef`/`#define`/`#endif`) are dropped. 1. Include guards (`#ifndef`/`#define`/`#endif`) are dropped, except the capacity macro (`#ifndef CULTMATH_MAX_SITES`,
`#define CULTMATH_MAX_SITES 128`, `#endif`), which becomes `const int CULTMATH_MAX_SITES = 128;`.
2. Functions taking `Texture2D`/`SamplerState` are dropped; GPU resources have 2. Functions taking `Texture2D`/`SamplerState` are dropped; GPU resources have
no CultMath analog. no CultMath analog.
3. File-scope `static const` becomes `const`. 3. File-scope `static const` becomes `const`.
@ -103,7 +104,8 @@ needs anything else, the test fails:
5. Struct fields gain `public`. HLSL struct members have no access-modifier 5. Struct fields gain `public`. HLSL struct members have no access-modifier
concept; a C# struct's fields default to `private`, which would hide them concept; a C# struct's fields default to `private`, which would hide them
from the mirror test's reflection-based field comparison. from the mirror test's reflection-based field comparison.
6. The file body is wrapped in a class, since C# has no free functions; HLSL 6. An array parameter `T name[CULTMATH_MAX_SITES]` becomes `T[] name`.
7. The file body is wrapped in a class, since C# has no free functions; HLSL
functions become private instance methods. functions become private instance methods.
HLSL constructs that stay outside the target and must not appear in the mirror: HLSL constructs that stay outside the target and must not appear in the mirror:
@ -156,6 +158,10 @@ hand. Regenerate them, and the golden fixture
$env:CULTMATH_WRITE_GLSL = "1"; dotnet test packages/cultmath/tests/CultMath.Tests --filter GlslMirrorTests $env:CULTMATH_WRITE_GLSL = "1"; dotnet test packages/cultmath/tests/CultMath.Tests --filter GlslMirrorTests
``` ```
Run it on Yggdrasil's `mcr.microsoft.com/dotnet/sdk:10.0` image (Ubuntu 24.04) only: that image owns the fixture's platform
families and `device-bounds-lavapipe.txt`, and `CULTMATH_WRITE_GLSL=1` fails on any other OS. Re-record the bounds there in
the same revision (`WRITE_BOUNDS=1`, `tools/device/run-lavapipe.sh`).
`GlslMirrorTests.CommittedGlslEqualsLowering` fails on any difference between `GlslMirrorTests.CommittedGlslEqualsLowering` fails on any difference between
either committed file and the lowering of the committed HLSL. These either committed file and the lowering of the committed HLSL. These
transformations, in order, are the complete list: transformations, in order, are the complete list:
@ -519,11 +525,12 @@ running `float4(unit normal toward the owner, distance)` and the radical plane o
the distance built from the offset `owner - p` and the weight difference `(w1 - w2)(w1 + w2)` so it does not the distance built from the offset `owner - p` and the weight difference `(w1 - w2)(w1 + w2)` so it does not
cancel for close sites seen from far; the cell boundary distance is the least over every site except the owner cancel for close sites seen from far; the cell boundary distance is the least over every site except the owner
(the fold's nearest), and a site coincident with the owner has no plane. The step trusts the caller's owner: a (the fold's nearest), and a site coincident with the owner has no plane. The step trusts the caller's owner: a
query outside the owner's cell reports distance 0 with that plane's normal, not an error. `power_border(p, sites)` is that loop. query outside the owner's cell reports distance 0 with that plane's normal, not an error. `power_border(p, sites)` is that loop;
the shader's `cultmath_power_border(p, sites, count)` is the same loop over a by-value array (see "Sites in a shader" below).
Cell spans refuse a non-finite query, site position or weight, and more than 2^24 sites (indices are floats). Cell spans refuse a non-finite query, site position or weight, and more than 2^24 sites (indices are floats).
`power_cells` and `power_cells` and
`multiplicative_cells` are that loop over a `ReadOnlySpan<float4>` and nothing else; a shader caller writes the `multiplicative_cells` are that loop over a `ReadOnlySpan<float4>` and nothing else; a shader caller writes the
same loop over its own buffer. Every site of an explicit span is visited, so no window and no weight-ratio range same loop over its own array. Every site of an explicit span is visited, so no window and no weight-ratio range
apply. `CultSiteCells` holds the two score fields and the two site indices (floats, -1 for none). apply. `CultSiteCells` holds the two score fields and the two site indices (floats, -1 for none).
`gabriel_margin(a, b, other)` is `(|other - (a + b) / 2| - r) / r` with `r = |a - b| / 2`: zero on the diameter `gabriel_margin(a, b, other)` is `(|other - (a + b) / 2| - r) / r` with `r = |a - b| / 2`: zero on the diameter
@ -557,8 +564,21 @@ The accepted range is boost 0 to `LatticeMaxBoost` (4, a weight ratio of 10:1),
and smoothing up to `LatticeMaxSmoothing` (1); outside it the function throws `ArgumentException`. `LatticeMaxRing` and smoothing up to `LatticeMaxSmoothing` (1); outside it the function throws `ArgumentException`. `LatticeMaxRing`
(37) is the proven greatest ring at the range edge. Measured over 24,000 points on orbiting bodies (six bodies, (37) is the proven greatest ring at the range edge. Measured over 24,000 points on orbiting bodies (six bodies,
sigma ratio 6:1, the ruled look and the range edge), the greatest ring visited has median 3, 99th percentile 7 and sigma ratio 6:1, the ruled look and the range edge), the greatest ring visited has median 3, 99th percentile 7 and
maximum 8 (49, 225 and 289 cells); `RingsStayWellInsideTheCap` asserts these. The function is C# only; the shader maximum 8 (49, 225 and 289 cells); `RingsStayWellInsideTheCap` asserts these.
twins are the next cut.
### Sites in a shader
A shader function cannot take a span, so `cultmath_power_border(p, sites, count)`, `cultmath_lattice_cell_distance(...)`
and `cultmath_weighted_lattice_cells(p, time, bodies, count, ...)` take `float4 sites[CULTMATH_MAX_SITES]` by value and the
count in use, with `count` in `[0, CULTMATH_MAX_SITES]`. `CULTMATH_MAX_SITES` is 128 unless the consumer defines it before
including the library (`#ifndef`): 128 covers every Aetheria zone within GLSL ES 3.00's 224 guaranteed fragment uniform
vectors, and loops run to `count`, so the capacity costs uniform space, not iterations. A caller whose sites live in a
buffer copies them into a local array first; the library takes no resource, accessor macro or uniform of its own. The preconditions the C#
functions throw for are the caller's in a shader (finite arguments, `cellSize > 0`, the stated ranges, `p` within 2^23
cells). The ring pass is bounded `k <= 37` and exits as C# does; the box pass recomputes each distance instead of caching
75 x 75 of them, which gives the same values. The C# mirror compiles the array as `T[]`, and
`HlslSourceCompatibilityTests.SiteArrayMirrorsMatchCSharp` compares the three functions through the span twins in
`SiteArrays` (reflection cannot pass a `ReadOnlySpan`); `CULTMATH_MAX_SITES` is a shader-only capacity with no C# twin.
## Intervals ## Intervals

View file

@ -600,14 +600,12 @@ CultCellular cultmath_cellular(vec3 p)
return result; return result;
} }
// Sites: a site is vec4(position.xyz, weight). A span is a loop over a per-site step, because a shader // Sites: a site is vec4(position.xyz, weight). A shader function cannot take a span (design.md, "Sites"), so a span is a
// function cannot take a span in the mirror (design.md, "Sites"). The consumer's loop, over its own buffer: // fixed-capacity array and a count (cultmath_power_border, below), or a loop over a per-site step:
// CultSiteCells cells = cultmath_site_cells_empty(); // CultSiteCells cells = cultmath_site_cells_empty();
// for (int i = 0; i < count; i++) // for (int i = 0; i < count; i++)
// cells = cultmath_site_cells_insert(cells, cultmath_power_score(p, sites[i]), i); // cells = cultmath_site_cells_insert(cells, cultmath_power_score(p, sites[i]), i);
// The exact power cell border is a second loop over the buffer, skipping the owner cells.nearestSite: // The exact power cell border is cultmath_power_border, the second loop over the sites that skips the owner cells.nearestSite.
// vec4 border = vec4(0.0, 0.0, 0.0, 1.0e30); int owner = int(cells.nearestSite);
// for (int k = 0; k < count; k++) if (k != owner) border = cultmath_power_border_step(p, sites[owner], sites[k], border);
// The step trusts the owner: a p outside the owner's cell reports distance 0 with that plane's normal, not an error. // The step trusts the owner: a p outside the owner's cell reports distance 0 with that plane's normal, not an error.
// Statement for statement with math.Sites.cs (power_score, multiplicative_score, site_cells_*, gabriel_margin). // Statement for statement with math.Sites.cs (power_score, multiplicative_score, site_cells_*, gabriel_margin).
struct CultSiteCells struct CultSiteCells
@ -688,21 +686,8 @@ float cultmath_gabriel_margin(vec3 a, vec3 b, vec3 other)
return (cultmath_distance(other, (a + b) * 0.5) - r) / r; return (cultmath_distance(other, (a + b) * 0.5) - r) / r;
} }
// Weighted lattice cells: the four per-cell steps of math.Lattice.cs, statement for statement. A shader function cannot take a // Weighted lattice cells: the four per-cell steps of math.Lattice.cs, statement for statement; cultmath_weighted_lattice_cells,
// span (design.md, "Sites"), so weighted_lattice_cells is the consumer's loop over its own body buffer, in two passes: // after them, is the whole function.
// vec2 q = p / cellSize; ivec2 c0 = (ivec2)floor(q); float f1 = 1.0e30, f2 = 1.0e30; int reach = 0;
// for (int k = 0; ; k++) { // ring pass: Chebyshev rings 0, 1, 2, ... around c0, exact exit
// if (k > 1 && 0.5 * (k - 1) >= max(f2, f1 + 2.0 * smoothing)) break; // k never reaches 38 in the accepted range
// for each cell on ring k: d = cultmath_lattice_distance(q, cultmath_lattice_site(cell, time, drift).xy, w, normal, stretch),
// w = cultmath_lattice_weight(site.z, border, boost, falloff), border = the .w of cultmath_power_border_step
// folded over the bodies (owner = the nearest body of the cell centre (cell + 0.5) * cellSize, z = 0);
// if (d < f1) { f2 = f1; f1 = d; } else if (d < f2) f2 = d; store d for the box pass
// reach = k; }
// float m = f1 + smoothing; // box pass: every stored cell, rows dz = -reach..reach, then dx = -reach..reach
// for each (dz, dx): m = cultmath_lattice_smooth_step(m, d, f1, smoothing); // strict < for the winning id (earlier cell wins ties)
// The preconditions the C# function throws for are the caller's in a shader: finite arguments, cellSize > 0, boost in [0, 4],
// falloff > 0, smoothing in (0, 1], stretch in [1, 2], p within 2^23 cells. The normal is normalised as C# does: divide by its
// largest component, then by its length (a zero normal means (1, 0) and needs stretch == 1). The ring bound is 37.
vec3 cultmath_lattice_site(vec2 cell, float time, float drift) vec3 cultmath_lattice_site(vec2 cell, float time, float drift)
{ {
ivec3 h = cultmath_pcg3d(ivec3(int(cell.x), int(cell.y), 0)); ivec3 h = cultmath_pcg3d(ivec3(int(cell.x), int(cell.y), 0));
@ -734,6 +719,90 @@ float cultmath_lattice_smooth_step(float m, float d, float f1, float smoothing)
return min(next, f1 + smoothing); return min(next, f1 + smoothing);
} }
// Sites by value: a shader function cannot take a span, so these take vec4 sites[CULTMATH_MAX_SITES] and the count in use, with
// count in [0, CULTMATH_MAX_SITES]. A consumer defines CULTMATH_MAX_SITES before including this file to change the capacity; loops
// run to count, so the capacity costs uniform space, not iterations. A caller whose sites live in a buffer copies them into a local array first.
// The preconditions the C# functions throw for are the caller's in a shader: finite arguments, cellSize > 0, boost in [0, 4],
// falloff > 0, smoothing in (0, 1], stretch in [1, 2], p within 2^23 cells. A zero stretchNormal means (1, 0) and needs stretch == 1.
// cultmath_weighted_lattice_cells is math.weighted_lattice_cells: the ring pass is bounded by k <= 37 and exits as C# does, and the
// box pass recomputes each distance instead of caching it (a 75 x 75 private array is not a shader option), with the same values.
#ifndef CULTMATH_MAX_SITES
#define CULTMATH_MAX_SITES 128
#endif
vec4 cultmath_power_border(vec3 p, vec4 sites[CULTMATH_MAX_SITES], int count)
{
CultSiteCells cells = cultmath_site_cells_empty();
for (int i = 0; i < count; i++)
cells = cultmath_site_cells_insert(cells, cultmath_power_score(p, sites[i]), i);
vec4 border = vec4(0.0, 0.0, 0.0, 1.0e30);
int owner = int(cells.nearestSite);
if (owner < 0)
return border;
for (int k = 0; k < count; k++)
if (k != owner)
border = cultmath_power_border_step(p, sites[owner], sites[k], border);
return border;
}
float cultmath_lattice_cell_distance(vec2 q, vec2 cell, float time, float drift, vec4 bodies[CULTMATH_MAX_SITES], int count,
float cellSize, float boost, float falloff, vec2 normal, float stretch)
{
vec3 site = cultmath_lattice_site(cell, time, drift);
float border = cultmath_power_border(vec3((cell + 0.5) * cellSize, 0.0), bodies, count).w;
return cultmath_lattice_distance(q, site.xy, cultmath_lattice_weight(site.z, border, boost, falloff), normal, stretch);
}
vec4 cultmath_weighted_lattice_cells(vec2 p, float time, vec4 bodies[CULTMATH_MAX_SITES], int count, float cellSize, float drift,
float boost, float falloff, float smoothing, vec2 stretchNormal, float stretch)
{
float scale = max(abs(stretchNormal.x), abs(stretchNormal.y));
vec2 normal = vec2(1.0, 0.0);
if (scale > 0.0)
{
normal = stretchNormal / scale;
normal = normal / length(normal);
}
vec2 q = p / cellSize;
vec2 origin = floor(q);
int cx0 = int(origin.x);
int cz0 = int(origin.y);
float f1 = 1.0e30;
float f2 = 1.0e30;
int reach = 0;
for (int k = 0; k <= 37; k++)
{
if (k > 1 && 0.5 * float(k - 1) >= max(f2, f1 + 2.0 * smoothing))
break;
for (int dz = -k; dz <= k; dz++)
{
int step = dz == -k || dz == k || k == 0 ? 1 : 2 * k;
for (int dx = -k; dx <= k; dx += step)
{
float d = cultmath_lattice_cell_distance(q, vec2(cx0 + dx, cz0 + dz), time, drift, bodies, count, cellSize, boost, falloff, normal, stretch);
if (d < f1) { f2 = f1; f1 = d; }
else if (d < f2) f2 = d;
}
}
reach = k;
}
float m = f1 + smoothing;
float best = 1.0e30;
float second = 1.0e30;
int winDx = 0;
int winDz = 0;
for (int dz = -reach; dz <= reach; dz++)
for (int dx = -reach; dx <= reach; dx++)
{
float d = cultmath_lattice_cell_distance(q, vec2(cx0 + dx, cz0 + dz), time, drift, bodies, count, cellSize, boost, falloff, normal, stretch);
if (d < best) { second = best; best = d; winDx = dx; winDz = dz; }
else if (d < second) second = d;
m = cultmath_lattice_smooth_step(m, d, f1, smoothing);
}
ivec4 id = cultmath_pcg4d(ivec4(cx0 + winDx, cz0 + winDz, 0, 0));
return vec4(best, second, m, float(uint(id.w) >> 8) * (1.0 / 16777216.0));
}
vec2 cultmath_segment_closest(vec2 point_value, vec2 start, vec2 end) vec2 cultmath_segment_closest(vec2 point_value, vec2 start, vec2 end)
{ {
vec2 segment = end - start; vec2 segment = end - start;

View file

@ -600,14 +600,12 @@ CultCellular cultmath_cellular(float3 p)
return result; return result;
} }
// Sites: a site is float4(position.xyz, weight). A span is a loop over a per-site step, because a shader // Sites: a site is float4(position.xyz, weight). A shader function cannot take a span (design.md, "Sites"), so a span is a
// function cannot take a span in the mirror (design.md, "Sites"). The consumer's loop, over its own buffer: // fixed-capacity array and a count (cultmath_power_border, below), or a loop over a per-site step:
// CultSiteCells cells = cultmath_site_cells_empty(); // CultSiteCells cells = cultmath_site_cells_empty();
// for (int i = 0; i < count; i++) // for (int i = 0; i < count; i++)
// cells = cultmath_site_cells_insert(cells, cultmath_power_score(p, sites[i]), i); // cells = cultmath_site_cells_insert(cells, cultmath_power_score(p, sites[i]), i);
// The exact power cell border is a second loop over the buffer, skipping the owner cells.nearestSite: // The exact power cell border is cultmath_power_border, the second loop over the sites that skips the owner cells.nearestSite.
// float4 border = float4(0.0, 0.0, 0.0, 1.0e30); int owner = (int)cells.nearestSite;
// for (int k = 0; k < count; k++) if (k != owner) border = cultmath_power_border_step(p, sites[owner], sites[k], border);
// The step trusts the owner: a p outside the owner's cell reports distance 0 with that plane's normal, not an error. // The step trusts the owner: a p outside the owner's cell reports distance 0 with that plane's normal, not an error.
// Statement for statement with math.Sites.cs (power_score, multiplicative_score, site_cells_*, gabriel_margin). // Statement for statement with math.Sites.cs (power_score, multiplicative_score, site_cells_*, gabriel_margin).
struct CultSiteCells struct CultSiteCells
@ -688,21 +686,8 @@ float cultmath_gabriel_margin(float3 a, float3 b, float3 other)
return (cultmath_distance(other, (a + b) * 0.5) - r) / r; return (cultmath_distance(other, (a + b) * 0.5) - r) / r;
} }
// Weighted lattice cells: the four per-cell steps of math.Lattice.cs, statement for statement. A shader function cannot take a // Weighted lattice cells: the four per-cell steps of math.Lattice.cs, statement for statement; cultmath_weighted_lattice_cells,
// span (design.md, "Sites"), so weighted_lattice_cells is the consumer's loop over its own body buffer, in two passes: // after them, is the whole function.
// float2 q = p / cellSize; int2 c0 = (int2)floor(q); float f1 = 1.0e30, f2 = 1.0e30; int reach = 0;
// for (int k = 0; ; k++) { // ring pass: Chebyshev rings 0, 1, 2, ... around c0, exact exit
// if (k > 1 && 0.5 * (k - 1) >= max(f2, f1 + 2.0 * smoothing)) break; // k never reaches 38 in the accepted range
// for each cell on ring k: d = cultmath_lattice_distance(q, cultmath_lattice_site(cell, time, drift).xy, w, normal, stretch),
// w = cultmath_lattice_weight(site.z, border, boost, falloff), border = the .w of cultmath_power_border_step
// folded over the bodies (owner = the nearest body of the cell centre (cell + 0.5) * cellSize, z = 0);
// if (d < f1) { f2 = f1; f1 = d; } else if (d < f2) f2 = d; store d for the box pass
// reach = k; }
// float m = f1 + smoothing; // box pass: every stored cell, rows dz = -reach..reach, then dx = -reach..reach
// for each (dz, dx): m = cultmath_lattice_smooth_step(m, d, f1, smoothing); // strict < for the winning id (earlier cell wins ties)
// The preconditions the C# function throws for are the caller's in a shader: finite arguments, cellSize > 0, boost in [0, 4],
// falloff > 0, smoothing in (0, 1], stretch in [1, 2], p within 2^23 cells. The normal is normalised as C# does: divide by its
// largest component, then by its length (a zero normal means (1, 0) and needs stretch == 1). The ring bound is 37.
float3 cultmath_lattice_site(float2 cell, float time, float drift) float3 cultmath_lattice_site(float2 cell, float time, float drift)
{ {
int3 h = cultmath_pcg3d(int3((int)cell.x, (int)cell.y, 0)); int3 h = cultmath_pcg3d(int3((int)cell.x, (int)cell.y, 0));
@ -734,6 +719,90 @@ float cultmath_lattice_smooth_step(float m, float d, float f1, float smoothing)
return min(next, f1 + smoothing); return min(next, f1 + smoothing);
} }
// Sites by value: a shader function cannot take a span, so these take float4 sites[CULTMATH_MAX_SITES] and the count in use, with
// count in [0, CULTMATH_MAX_SITES]. A consumer defines CULTMATH_MAX_SITES before including this file to change the capacity; loops
// run to count, so the capacity costs uniform space, not iterations. A caller whose sites live in a buffer copies them into a local array first.
// The preconditions the C# functions throw for are the caller's in a shader: finite arguments, cellSize > 0, boost in [0, 4],
// falloff > 0, smoothing in (0, 1], stretch in [1, 2], p within 2^23 cells. A zero stretchNormal means (1, 0) and needs stretch == 1.
// cultmath_weighted_lattice_cells is math.weighted_lattice_cells: the ring pass is bounded by k <= 37 and exits as C# does, and the
// box pass recomputes each distance instead of caching it (a 75 x 75 private array is not a shader option), with the same values.
#ifndef CULTMATH_MAX_SITES
#define CULTMATH_MAX_SITES 128
#endif
float4 cultmath_power_border(float3 p, float4 sites[CULTMATH_MAX_SITES], int count)
{
CultSiteCells cells = cultmath_site_cells_empty();
for (int i = 0; i < count; i++)
cells = cultmath_site_cells_insert(cells, cultmath_power_score(p, sites[i]), i);
float4 border = float4(0.0, 0.0, 0.0, 1.0e30);
int owner = (int)cells.nearestSite;
if (owner < 0)
return border;
for (int k = 0; k < count; k++)
if (k != owner)
border = cultmath_power_border_step(p, sites[owner], sites[k], border);
return border;
}
float cultmath_lattice_cell_distance(float2 q, float2 cell, float time, float drift, float4 bodies[CULTMATH_MAX_SITES], int count,
float cellSize, float boost, float falloff, float2 normal, float stretch)
{
float3 site = cultmath_lattice_site(cell, time, drift);
float border = cultmath_power_border(float3((cell + 0.5) * cellSize, 0.0), bodies, count).w;
return cultmath_lattice_distance(q, site.xy, cultmath_lattice_weight(site.z, border, boost, falloff), normal, stretch);
}
float4 cultmath_weighted_lattice_cells(float2 p, float time, float4 bodies[CULTMATH_MAX_SITES], int count, float cellSize, float drift,
float boost, float falloff, float smoothing, float2 stretchNormal, float stretch)
{
float scale = max(abs(stretchNormal.x), abs(stretchNormal.y));
float2 normal = float2(1.0, 0.0);
if (scale > 0.0)
{
normal = stretchNormal / scale;
normal = normal / length(normal);
}
float2 q = p / cellSize;
float2 origin = floor(q);
int cx0 = (int)origin.x;
int cz0 = (int)origin.y;
float f1 = 1.0e30;
float f2 = 1.0e30;
int reach = 0;
for (int k = 0; k <= 37; k++)
{
if (k > 1 && 0.5 * (float)(k - 1) >= max(f2, f1 + 2.0 * smoothing))
break;
for (int dz = -k; dz <= k; dz++)
{
int step = dz == -k || dz == k || k == 0 ? 1 : 2 * k;
for (int dx = -k; dx <= k; dx += step)
{
float d = cultmath_lattice_cell_distance(q, float2(cx0 + dx, cz0 + dz), time, drift, bodies, count, cellSize, boost, falloff, normal, stretch);
if (d < f1) { f2 = f1; f1 = d; }
else if (d < f2) f2 = d;
}
}
reach = k;
}
float m = f1 + smoothing;
float best = 1.0e30;
float second = 1.0e30;
int winDx = 0;
int winDz = 0;
for (int dz = -reach; dz <= reach; dz++)
for (int dx = -reach; dx <= reach; dx++)
{
float d = cultmath_lattice_cell_distance(q, float2(cx0 + dx, cz0 + dz), time, drift, bodies, count, cellSize, boost, falloff, normal, stretch);
if (d < best) { second = best; best = d; winDx = dx; winDz = dz; }
else if (d < second) second = d;
m = cultmath_lattice_smooth_step(m, d, f1, smoothing);
}
int4 id = cultmath_pcg4d(int4(cx0 + winDx, cz0 + winDz, 0, 0));
return float4(best, second, m, (float)((uint)id.w >> 8) * (1.0 / 16777216.0));
}
float2 cultmath_segment_closest(float2 point_value, float2 start, float2 end) float2 cultmath_segment_closest(float2 point_value, float2 start, float2 end)
{ {
float2 segment = end - start; float2 segment = end - start;

View file

@ -200,7 +200,7 @@ public static partial class math
return new float4(best, second, m, cellular_unit(id.w)); return new float4(best, second, m, cellular_unit(id.w));
} }
private static float lattice_cell_distance( internal static float lattice_cell_distance(
float2 q, float2 cell, float time, float drift, ReadOnlySpan<float4> bodies, float cellSize, float boost, float falloff, float2 q, float2 cell, float time, float drift, ReadOnlySpan<float4> bodies, float cellSize, float boost, float falloff,
float2 normal, float stretch) float2 normal, float stretch)
{ {

View file

@ -70,8 +70,9 @@ internal static class GlslLowering
var source = hlsl.ReplaceLineEndings("\n"); var source = hlsl.ReplaceLineEndings("\n");
// 2. Include guards are dropped, and so are the functions taking Texture2D/SamplerState, exactly // 2. Include guards are dropped, and so are the functions taking Texture2D/SamplerState, exactly
// as the C# mirror drops them; the host shader samples its own textures. // as the C# mirror drops them; the host shader samples its own textures. The capacity macro
source = Regex.Replace(source, @"(?m)^[ \t]*#(ifndef|define|endif)\b.*$", string.Empty); // (#ifndef CULTMATH_MAX_SITES ... #endif) stays: GLSL has the preprocessor, and a consumer overrides it.
source = HlslSourceCompatibilityTests.DropGuards(source, (name, value) => $"#ifndef {name}\n#define {name} {value}\n#endif");
source = Regex.Replace(source, @"(?ms)^\w+ \w+\([^)]*\b(Texture2D|SamplerState)\b[^)]*\)\s*\{.*?^\}[ \t]*$", string.Empty); source = Regex.Replace(source, @"(?ms)^\w+ \w+\([^)]*\b(Texture2D|SamplerState)\b[^)]*\)\s*\{.*?^\}[ \t]*$", string.Empty);
// 3. Type names: floatN, intN, uintN and boolN become vecN, ivecN, uvecN and bvecN, and float3x3 // 3. Type names: floatN, intN, uintN and boolN become vecN, ivecN, uvecN and bvecN, and float3x3

View file

@ -0,0 +1,60 @@
using System.Reflection;
using System.Text.RegularExpressions;
using Xunit;
namespace CultMath.Tests;
public sealed partial class GlslMirrorTests
{
private const string Weighted = "cultmath_weighted_lattice_cells(";
private const string SmoothStep = "cultmath_lattice_smooth_step(";
// Edits of the shader text that each break one rule of the lattice loop. M7 (the exit one ring late) is absent: by the
// window-independence proof a later exit visits more cells and changes nothing, so no case can kill it.
private static readonly (string Name, string Function, string Old, string New, int Kills)[] LatticeMutants =
{
("M1 exit one ring early", Weighted, "0.5 * (float)(k - 1) >= max(f2", "0.5 * (float)k >= max(f2", 3),
("M2 exit ignores the smoothing band", Weighted, ">= max(f2, f1 + 2.0 * smoothing))", ">= f2)", 1),
("M3 no pre-normalisation of the normal", Weighted, "normal = stretchNormal / scale;", "normal = stretchNormal;", 1),
("M4 no unit length for the normal", Weighted, "normal = normal / length(normal);", "normal = normal * 1.0;", 1),
("M5 smooth step without its clamp", SmoothStep, "return min(next, f1 + smoothing);", "return next;", 1),
("M6 box pass skips its first row", Weighted, "for (int dz = -reach; dz <= reach; dz++)", "for (int dz = -reach + 1; dz <= reach; dz++)", 1),
};
/// <summary>
/// Each rule of the lattice loop is pinned by the committed fixture's own cases: the shader text with one rule broken (a
/// mutant mirror, the text a device runs) returns another result than the fixture on at least the stated number of
/// cases of the family that exercises it, the exit one ring early on at least three. The device judge holds the same
/// cases to their recorded bound, so a mutant these cases separate fails it too.
/// </summary>
[Fact]
public void TheFixtureCasesSeparateEveryLatticeMutantFromTheShader()
{
var families = FixtureFamilies();
foreach (var (name, function, old, replacement, kills) in LatticeMutants)
{
var (assembly, errors) = HlslSourceCompatibilityTests.CompileShaderMirror(mutate: text =>
{
Assert.True(text.Split(old).Length == 2, $"{name}: the shader text no longer holds exactly one \"{old}\"");
return text.Replace(old, replacement);
});
Assert.True(assembly is not null, name + ": " + string.Join(Environment.NewLine, errors));
var shaderType = assembly!.GetType("CultMathHlsl.HlslShader")!;
var shader = Activator.CreateInstance(shaderType);
var mirror = shaderType.GetMethod(function[..^1], BindingFlags.Instance | BindingFlags.NonPublic)!;
var f = Array.FindIndex(Families, family => family.Glsl.Contains(function));
var method = Resolve(Families[f].Glsl);
var killed = 0;
foreach (var entry in families[f]!["cases"]!.AsArray())
{
var sides = Sides(entry);
if (Hex(new[] { mirror.Invoke(shader, Arguments(method, sides[0]))! }) != sides[1])
killed++;
}
output.WriteLine($"{name}: separated on {killed} of {Points} cases of {Families[f].Glsl}");
Assert.True(killed >= kills, $"{name}: the {Families[f].Glsl} fixture cases separate it on {killed} of {Points}, fewer than {kills}");
}
}
}

View file

@ -22,7 +22,11 @@ public sealed partial class GlslMirrorTests
public GlslMirrorTests(ITestOutputHelper output) => this.output = output; public GlslMirrorTests(ITestOutputHelper output) => this.output = output;
private static bool Write => Environment.GetEnvironmentVariable("CULTMATH_WRITE_GLSL") == "1"; // The fixture is written by Yggdrasil's mcr.microsoft.com/dotnet/sdk:10.0 image (Ubuntu 24.04) only: its platform families record
// that platform's libm, so a write from another OS would move them.
private static bool Write => Environment.GetEnvironmentVariable("CULTMATH_WRITE_GLSL") == "1"
? OperatingSystem.IsLinux() ? true : throw new InvalidOperationException("CULTMATH_WRITE_GLSL=1 writes glsl-parity.json, whose platform families belong to Yggdrasil's Ubuntu image (mcr.microsoft.com/dotnet/sdk:10.0): regenerate it there.")
: false;
private static string Root => HlslSourceCompatibilityTests.FindCultMathRoot(); private static string Root => HlslSourceCompatibilityTests.FindCultMathRoot();
@ -497,6 +501,14 @@ static int4 clamp(int4 x, int4 a, int4 b) { if (a.x > b.x || a.y > b.y || a.z >
return (int)scalars.Dequeue(); return (int)scalars.Dequeue();
if (type == typeof(uint)) if (type == typeof(uint))
return scalars.Dequeue(); return scalars.Dequeue();
if (type.IsArray)
{
var array = Array.CreateInstance(type.GetElementType()!, SiteArrays.Capacity);
for (var i = 0; i < array.Length; i++)
array.SetValue(FromScalars(type.GetElementType()!, scalars), i);
return array;
}
var value = Activator.CreateInstance(type)!; var value = Activator.CreateInstance(type)!;
foreach (var field in type.GetFields().Where(f => !f.IsStatic)) foreach (var field in type.GetFields().Where(f => !f.IsStatic))
field.SetValue(value, FromScalars(field.FieldType, scalars)); field.SetValue(value, FromScalars(field.FieldType, scalars));
@ -872,14 +884,26 @@ static int4 clamp(int4 x, int4 a, int4 b) { if (a.x > b.x || a.y > b.y || a.z >
}), }),
// The smooth step: d exactly at f1 + 2 smoothing one in four (the equal branch), near it one in four, the // The smooth step: d exactly at f1 + 2 smoothing one in four (the equal branch), near it one in four, the
// smoothing near 0 and at 1, m anywhere up to f1 + smoothing. // smoothing near 0 and at 1, m anywhere up to f1 + smoothing.
// One draw in four is the cap point: m = f1 + smoothing exactly and d = f1 + smoothing (1 + U(0.3, 0.9)) with smoothing in
// [0.5, 1], where next exceeds f1 + smoothing and the min clamps; a step that returns next reads another function there.
new Family("float cultmath_lattice_smooth_step(float, float, float, float)", "ulp-bounded", r => new Family("float cultmath_lattice_smooth_step(float, float, float, float)", "ulp-bounded", r =>
{ {
if (r.Next(4) == 0)
{
var cappedSmoothing = Uniform(r, 0.5f, 1.0f);
var cappedF1 = Uniform(r, 0.0f, 10.0f);
return new object[] { cappedF1 + cappedSmoothing, cappedF1 + cappedSmoothing * (1.0f + Uniform(r, 0.3f, 0.9f)), cappedF1, cappedSmoothing };
}
var smoothing = r.Next(5) switch { 0 => 1.0f, 1 => Uniform(r, 1.0e-3f, 1.0e-2f), _ => Uniform(r, 0.01f, 1.0f) }; var smoothing = r.Next(5) switch { 0 => 1.0f, 1 => Uniform(r, 1.0e-3f, 1.0e-2f), _ => Uniform(r, 0.01f, 1.0f) };
var f1 = Uniform(r, 0.0f, 10.0f); var f1 = Uniform(r, 0.0f, 10.0f);
var edge = f1 + 2.0f * smoothing; var edge = f1 + 2.0f * smoothing;
var d = r.Next(4) switch { 0 => edge, 1 => edge * Uniform(r, 0.99f, 1.01f), _ => Uniform(r, f1, edge + 1.0f) }; var d = r.Next(4) switch { 0 => edge, 1 => edge * Uniform(r, 0.99f, 1.01f), _ => Uniform(r, f1, edge + 1.0f) };
return new object[] { Uniform(r, f1, f1 + smoothing), d, f1, smoothing }; return new object[] { Uniform(r, f1, f1 + smoothing), d, f1, smoothing };
}), }),
// The site-array functions: a fixed-capacity array and a count, killed on the device (the lattice at rings up to 17).
new Family($"vec4 cultmath_power_border(vec3, vec4[{SiteArrays.Capacity}], int)", "ulp-bounded", SiteArrays.PowerBorderArguments),
new Family($"vec4 cultmath_weighted_lattice_cells(vec2, float, vec4[{SiteArrays.Capacity}], int, float, float, float, float, float, vec2, float)", "ulp-bounded", SiteArrays.LatticeArguments),
}).ToArray(); }).ToArray();
// A point of magnitude m, m log-uniform in [lo, hi]: one component at +-m, the other two uniform in [-m, m], // A point of magnitude m, m log-uniform in [lo, hi]: one component at +-m, the other two uniform in [-m, m],
@ -988,6 +1012,7 @@ static int4 clamp(int4 x, int4 a, int4 b) { if (a.x > b.x || a.y > b.y || a.z >
["vec4"] = typeof(float4), ["ivec3"] = typeof(int3), ["ivec4"] = typeof(int4), ["vec4"] = typeof(float4), ["ivec3"] = typeof(int3), ["ivec4"] = typeof(int4),
["SpanPath"] = typeof(SpanPath), ["SpanStage"] = typeof(SpanStage), ["SpanPath"] = typeof(SpanPath), ["SpanStage"] = typeof(SpanStage),
["CultSiteCells"] = typeof(CultSiteCells), ["CultSiteCells"] = typeof(CultSiteCells),
[$"vec4[{SiteArrays.Capacity}]"] = typeof(float4[]),
}; };
// The C# math method a GLSL signature names: cultmath_ dropped, parameter types mapped. // The C# math method a GLSL signature names: cultmath_ dropped, parameter types mapped.
@ -996,6 +1021,7 @@ static int4 clamp(int4 x, int4 a, int4 b) { if (a.x > b.x || a.y > b.y || a.z >
var match = Regex.Match(glsl, @"^\w+ cultmath_(\w+)\(([^)]*)\)$"); var match = Regex.Match(glsl, @"^\w+ cultmath_(\w+)\(([^)]*)\)$");
var types = match.Groups[2].Value.Split(", ").Select(t => GlslTypes[t]).ToArray(); var types = match.Groups[2].Value.Split(", ").Select(t => GlslTypes[t]).ToArray();
return typeof(math).GetMethod(match.Groups[1].Value, BindingFlags.Public | BindingFlags.Static, null, types, null) return typeof(math).GetMethod(match.Groups[1].Value, BindingFlags.Public | BindingFlags.Static, null, types, null)
?? typeof(SiteArrays).GetMethod(match.Groups[1].Value, BindingFlags.NonPublic | BindingFlags.Static, null, types, null)
?? throw new InvalidOperationException("no C# math counterpart for " + glsl); ?? throw new InvalidOperationException("no C# math counterpart for " + glsl);
} }
@ -1011,6 +1037,8 @@ static int4 clamp(int4 x, int4 a, int4 b) { if (a.x > b.x || a.y > b.y || a.z >
// the same read as GLSL's columns. // the same read as GLSL's columns.
private static IEnumerable<object> Scalars(object value) => value is float or int or uint private static IEnumerable<object> Scalars(object value) => value is float or int or uint
? new[] { value } ? new[] { value }
: value is Array array
? array.Cast<object>().SelectMany(Scalars)
: value is float3x3 matrix : value is float3x3 matrix
? new object[] { matrix._m00, matrix._m01, matrix._m02, matrix._m10, matrix._m11, matrix._m12, matrix._m20, matrix._m21, matrix._m22 } ? new object[] { matrix._m00, matrix._m01, matrix._m02, matrix._m10, matrix._m11, matrix._m12, matrix._m20, matrix._m21, matrix._m22 }
: value.GetType().GetFields().Where(f => !f.IsStatic).SelectMany(f => Scalars(f.GetValue(value)!)); : value.GetType().GetFields().Where(f => !f.IsStatic).SelectMany(f => Scalars(f.GetValue(value)!));

View file

@ -70,7 +70,7 @@ public sealed class HlslSourceCompatibilityTests
shader.GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic, null, parameters, null); shader.GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic, null, parameters, null);
// Shader constants with no C# twin of the same name (empty when every constant has one). // Shader constants with no C# twin of the same name (empty when every constant has one).
private static readonly string[] ConstantsWithoutTwin = Array.Empty<string>(); private static readonly string[] ConstantsWithoutTwin = { "CULTMATH_MAX_SITES" };
/// <summary> /// <summary>
/// Every static const of the compiled HLSL mirror (a static literal CULTMATH_* field, whatever its access) is the /// Every static const of the compiled HLSL mirror (a static literal CULTMATH_* field, whatever its access) is the
@ -85,7 +85,7 @@ public sealed class HlslSourceCompatibilityTests
Assert.True(assembly is not null, string.Join(Environment.NewLine, errors)); Assert.True(assembly is not null, string.Join(Environment.NewLine, errors));
var shader = assembly!.GetType("CultMathHlsl.HlslShader")!; var shader = assembly!.GetType("CultMathHlsl.HlslShader")!;
var constants = shader.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static) var constants = shader.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)
.Where(f => f.IsLiteral && f.Name.StartsWith("CULTMATH_", StringComparison.Ordinal)) .Where(f => f.IsLiteral && f.FieldType == typeof(float) && f.Name.StartsWith("CULTMATH_", StringComparison.Ordinal))
.ToArray(); .ToArray();
Assert.True(constants.Length >= 10, $"only {constants.Length} shader constants were found by reflection"); Assert.True(constants.Length >= 10, $"only {constants.Length} shader constants were found by reflection");
foreach (var constant in constants) foreach (var constant in constants)
@ -183,7 +183,7 @@ public sealed class HlslSourceCompatibilityTests
var mismatches = new List<string>(); var mismatches = new List<string>();
var compared = new HashSet<string>(); var compared = new HashSet<string>();
foreach (var mirror in shaderType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).Where(m => m.Name.StartsWith("cultmath_"))) foreach (var mirror in shaderType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).Where(m => m.Name.StartsWith("cultmath_") && !m.GetParameters().Any(p => p.ParameterType.IsArray)))
{ {
var types = mirror.GetParameters().Select(p => p.ParameterType).ToArray(); var types = mirror.GetParameters().Select(p => p.ParameterType).ToArray();
var counterpart = Twin(shaderType, mirror.Name["cultmath_".Length..], types); var counterpart = Twin(shaderType, mirror.Name["cultmath_".Length..], types);
@ -204,9 +204,67 @@ public sealed class HlslSourceCompatibilityTests
} }
Assert.True(mismatches.Count == 0, $"{mismatches.Count} mismatches:{Environment.NewLine}{string.Join(Environment.NewLine, mismatches.Take(12))}"); Assert.True(mismatches.Count == 0, $"{mismatches.Count} mismatches:{Environment.NewLine}{string.Join(Environment.NewLine, mismatches.Take(12))}");
// The three site-array functions are compared by SiteArrayMirrorsMatchCSharp, which draws their counts and arrays.
Assert.Equal(140, compared.Count); Assert.Equal(140, compared.Count);
} }
/// <summary>
/// The shader functions that take a site array and a count are bit-equal to C#'s span functions on the same
/// arguments: at least 64 compared cases each, counts 0 to the capacity with filler after the count (a loop bound that
/// ignores the count reads it), the lattice at rings up to 17. A case C# refuses is skipped and counted.
/// </summary>
[Fact]
public void SiteArrayMirrorsMatchCSharp()
{
var (assembly, errors) = CompileShaderMirror();
Assert.True(assembly is not null, string.Join(Environment.NewLine, errors));
var shaderType = assembly!.GetType("CultMathHlsl.HlslShader")!;
var shader = Activator.CreateInstance(shaderType);
Assert.Equal(17, SiteArrays.FarPoints[0].Ring);
Assert.All(SiteArrays.FarPoints, far => Assert.InRange(far.Ring, 8, LatticeMaxRing));
var draws = new (string Name, Func<System.Random, object[]> Draw)[]
{
("power_border", SiteArrays.PowerBorderArguments),
("lattice_cell_distance", SiteArrays.LatticeCellDistanceArguments),
("weighted_lattice_cells", SiteArrays.LatticeArguments),
};
foreach (var (name, draw) in draws)
{
var mirror = shaderType.GetMethod("cultmath_" + name, BindingFlags.Instance | BindingFlags.NonPublic)!;
var twin = typeof(SiteArrays).GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic)!;
Assert.Equal(twin.GetParameters().Select(p => p.ParameterType), mirror.GetParameters().Select(p => p.ParameterType));
var random = new System.Random(0x517E);
int compared = 0, refused = 0;
for (var i = 0; i < 160; i++)
{
var args = draw(random);
object expected;
try { expected = twin.Invoke(null, args)!; }
catch (TargetInvocationException e) when (e.InnerException is ArgumentException) { refused++; continue; }
var actual = Values(mirror.Invoke(shader, args)!);
Assert.True(BitwiseEqual(Values(expected), actual),
$"{name} case {i}: C# {string.Join(" ", Values(expected).Select(v => v.Value))}, mirror {string.Join(" ", actual.Select(v => v.Value))}");
compared++;
}
Assert.True(compared >= 64, $"{name} compared only {compared} cases ({refused} refused)");
}
}
/// <summary>A consumer defines CULTMATH_MAX_SITES before including the library; the mirror compiles with the capacity changed.</summary>
[Fact]
public void TheSiteCapacityIsOverridable()
{
var (assembly, errors) = CompileShaderMirror(defines: new Dictionary<string, int> { ["CULTMATH_MAX_SITES"] = 4 });
Assert.True(assembly is not null, string.Join(Environment.NewLine, errors));
var shaderType = assembly!.GetType("CultMathHlsl.HlslShader")!;
Assert.Equal(4, (int)shaderType.GetField("CULTMATH_MAX_SITES", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)!.GetRawConstantValue()!);
var sites = new[] { new float4(0, 0, 0, 1), new float4(10, 0, 0, 1), new float4(0, 10, 0, 2), new float4(-10, 0, 0, 1) };
var border = (float4)shaderType.GetMethod("cultmath_power_border", BindingFlags.Instance | BindingFlags.NonPublic)!.Invoke(Activator.CreateInstance(shaderType), new object[] { new float3(1, 1, 0), sites, 4 })!;
Assert.Equal(power_border(new float3(1, 1, 0), sites), border);
}
/// <summary> /// <summary>
/// The 1500 arguments (a, b, lo, hi, t0, t1) at which integral_clamp_linear's zero-length rule, its lo /// The 1500 arguments (a, b, lo, hi, t0, t1) at which integral_clamp_linear's zero-length rule, its lo
/// normalisation and its infinite bounds all bite: a in {-2.5, 0, 1.5}, b in {-1.5, -0, 0, 2}, lo in /// normalisation and its infinite bounds all bite: a in {-2.5, 0, 1.5}, b in {-1.5, -0, 0, 2}, lo in
@ -682,14 +740,23 @@ public sealed class HlslSourceCompatibilityTests
: string.Empty); : string.Empty);
} }
/// <summary>
/// The text without its include-guard lines. A capacity macro (<c>#ifndef X</c>, <c>#define X n</c>, <c>#endif</c>, a
/// consumer-overridable default) is not a guard: <paramref name="capacity"/> gives the text it becomes, from its name and value.
/// </summary>
internal static string DropGuards(string source, Func<string, string, string> capacity) =>
Regex.Replace(source, @"(?m)^#ifndef (CULTMATH_MAX_\w+)\r?\n#define \1 (\d+)\r?\n#endif[ \t]*$|^[ \t]*#(ifndef|define|endif)\b.*$",
match => match.Groups[1].Success ? capacity(match.Groups[1].Value, match.Groups[2].Value) : string.Empty);
/// <summary> /// <summary>
/// The documented HLSL-to-C# transformations, and nothing else. <paramref name="members"/> is C# source a /// The documented HLSL-to-C# transformations, and nothing else. <paramref name="members"/> is C# source a
/// test supplies, placed first in the class body: it is not a transformation of the HLSL. /// test supplies, placed first in the class body: it is not a transformation of the HLSL.
/// </summary> /// </summary>
internal static string TransformHlslToCSharp(string hlsl, string members = "") internal static string TransformHlslToCSharp(string hlsl, string members = "", IReadOnlyDictionary<string, int>? defines = null)
{ {
// 1. Include guards (#ifndef/#define/#endif) are dropped; C# has no textual include. // 1. Include guards (#ifndef/#define/#endif) are dropped; C# has no textual include. A capacity macro
var source = Regex.Replace(hlsl, @"(?m)^\s*#(ifndef|define|endif)\b.*$", string.Empty); // (#ifndef X, #define X n, #endif) becomes `const int X = n;`, or the value a test defines for X.
var source = DropGuards(hlsl, (name, value) => $"const int {name} = {(defines is not null && defines.TryGetValue(name, out var defined) ? defined : value)};");
// 2. Functions taking Texture2D/SamplerState resources are dropped; GPU resources have // 2. Functions taking Texture2D/SamplerState resources are dropped; GPU resources have
// no CultMath analog. // no CultMath analog.
@ -708,14 +775,19 @@ public sealed class HlslSourceCompatibilityTests
source = Regex.Replace(source, @"(?ms)(struct\s+\w+\s*\{)(.*?)(\};)", m => source = Regex.Replace(source, @"(?ms)(struct\s+\w+\s*\{)(.*?)(\};)", m =>
m.Groups[1].Value + Regex.Replace(m.Groups[2].Value, @"(?m)^(\s*)(\w+\s+\w+;)", "$1public $2") + m.Groups[3].Value); m.Groups[1].Value + Regex.Replace(m.Groups[2].Value, @"(?m)^(\s*)(\w+\s+\w+;)", "$1public $2") + m.Groups[3].Value);
// 6. The file body is wrapped in a class; C# has no free functions. // 6. An array parameter `T name[N]` becomes `T[] name`; the mirror takes the array whole and the count beside it.
source = Regex.Replace(source, @"\b(\w+) (\w+)\[CULTMATH_MAX_\w+\]", "$1[] $2");
// 7. The file body is wrapped in a class; C# has no free functions.
return "using CultMath;\nusing static CultMath.math;\nnamespace CultMathHlsl;\npublic class HlslShader\n{\n" + members + source + "\n}\n"; return "using CultMath;\nusing static CultMath.math;\nnamespace CultMathHlsl;\npublic class HlslShader\n{\n" + members + source + "\n}\n";
} }
internal static (Assembly? Assembly, IReadOnlyList<string> Errors) CompileShaderMirror(string members = "") internal static (Assembly? Assembly, IReadOnlyList<string> Errors) CompileShaderMirror(string members = "", IReadOnlyDictionary<string, int>? defines = null, Func<string, string>? mutate = null)
{ {
var hlsl = ReadShaderSource(FindCultMathRoot()); var hlsl = ReadShaderSource(FindCultMathRoot());
var tree = CSharpSyntaxTree.ParseText(TransformHlslToCSharp(hlsl, members)); if (mutate is not null)
hlsl = mutate(hlsl);
var tree = CSharpSyntaxTree.ParseText(TransformHlslToCSharp(hlsl, members, defines));
var references = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")!) var references = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")!)
.Split(Path.PathSeparator) .Split(Path.PathSeparator)
.Append(typeof(math).Assembly.Location) .Append(typeof(math).Assembly.Location)

View file

@ -9,19 +9,19 @@ namespace CultMath.Tests;
/// </summary> /// </summary>
public sealed class LatticeCellsTests public sealed class LatticeCellsTests
{ {
private const float Cell = 260.0f; internal const float Cell = 260.0f;
private const float Falloff = 120.0f; internal const float Falloff = 120.0f;
private const float Drift = 0.7f; internal const float Drift = 0.7f;
private static readonly float2 Normal = new(0.6f, 0.8f); internal static readonly float2 Normal = new(0.6f, 0.8f);
private sealed record Look(float Boost, float Stretch, float Smoothing, float Falloff = LatticeCellsTests.Falloff); internal sealed record Look(float Boost, float Stretch, float Smoothing, float Falloff = LatticeCellsTests.Falloff);
private static readonly Look[] Looks = internal static readonly Look[] Looks =
{ {
new(1.6f, 1.0f, 0.35f), new(1.6f, 2.0f, 0.35f), new(LatticeMaxBoost, LatticeMaxStretch, LatticeMaxSmoothing), new(0.0f, 1.0f, 0.05f), new(1.6f, 1.0f, 0.35f), new(1.6f, 2.0f, 0.35f), new(LatticeMaxBoost, LatticeMaxStretch, LatticeMaxSmoothing), new(0.0f, 1.0f, 0.05f),
}; };
private static float4[] Bodies(float t) internal static float4[] Bodies(float t)
{ {
var bodies = new float4[6]; var bodies = new float4[6];
for (var i = 0; i < bodies.Length; i++) for (var i = 0; i < bodies.Length; i++)
@ -41,7 +41,7 @@ public sealed class LatticeCellsTests
} }
// Folds F1, F2, id and smooth over the box of the given radius in absolute row-major order. // Folds F1, F2, id and smooth over the box of the given radius in absolute row-major order.
private static float4 BoxFold(float2 p, float time, float4[] bodies, Look look, int radius, internal static float4 BoxFold(float2 p, float time, float4[] bodies, Look look, int radius,
Dictionary<(int, int), float> weights, float2? unitNormal = null) Dictionary<(int, int), float> weights, float2? unitNormal = null)
{ {
var q = p / Cell; var q = p / Cell;
@ -71,10 +71,10 @@ public sealed class LatticeCellsTests
return new float4(f1, f2, m, cellular_unit(id.w)); return new float4(f1, f2, m, cellular_unit(id.w));
} }
private static float4 Call(float2 p, float time, float4[] bodies, Look look, float2? normal = null) => internal static float4 Call(float2 p, float time, float4[] bodies, Look look, float2? normal = null) =>
weighted_lattice_cells(p, time, bodies, Cell, Drift, look.Boost, look.Falloff, look.Smoothing, normal ?? Normal, look.Stretch); weighted_lattice_cells(p, time, bodies, Cell, Drift, look.Boost, look.Falloff, look.Smoothing, normal ?? Normal, look.Stretch);
private static float2 RandomPoint(System.Random r) => internal static float2 RandomPoint(System.Random r) =>
new((r.NextSingle() * 2.0f - 1.0f) * 1500.0f, (r.NextSingle() * 2.0f - 1.0f) * 1500.0f); new((r.NextSingle() * 2.0f - 1.0f) * 1500.0f, (r.NextSingle() * 2.0f - 1.0f) * 1500.0f);
[Fact] [Fact]
@ -97,9 +97,9 @@ public sealed class LatticeCellsTests
} }
} }
private delegate bool Stop(int k, float f1, float f2, Look look); internal delegate bool Stop(int k, float f1, float f2, Look look);
private static readonly (string name, Stop stop)[] Rules = internal static readonly (string name, Stop stop)[] Rules =
{ {
("correct", (k, f1, f2, l) => k > 1 && 0.5f * (k - 1) >= max(f2, f1 + 2.0f * l.Smoothing)), ("correct", (k, f1, f2, l) => k > 1 && 0.5f * (k - 1) >= max(f2, f1 + 2.0f * l.Smoothing)),
("cutoff F1 + k", (k, f1, f2, l) => k > 1 && 0.5f * (k - 1) >= max(f2, f1 + l.Smoothing)), ("cutoff F1 + k", (k, f1, f2, l) => k > 1 && 0.5f * (k - 1) >= max(f2, f1 + l.Smoothing)),
@ -109,7 +109,7 @@ public sealed class LatticeCellsTests
}; };
// The radius of the box a search with the given exit rule visits (rings 0 to k - 1 when it stops before ring k). // The radius of the box a search with the given exit rule visits (rings 0 to k - 1 when it stops before ring k).
private static int Radius(float2 p, float time, float4[] bodies, Look look, Stop stop, internal static int Radius(float2 p, float time, float4[] bodies, Look look, Stop stop,
Dictionary<(int, int), float> weights) Dictionary<(int, int), float> weights)
{ {
var q = p / Cell; var q = p / Cell;

View file

@ -0,0 +1,160 @@
using CultMath;
using static CultMath.math;
namespace CultMath.Tests;
/// <summary>
/// The shader's site-array functions as C# twins. A shader function cannot take a span, so
/// <c>cultmath_power_border</c>, <c>cultmath_lattice_cell_distance</c> and <c>cultmath_weighted_lattice_cells</c> take
/// <c>float4 sites[CULTMATH_MAX_SITES]</c> and a count; reflection cannot pass a <see cref="ReadOnlySpan{T}"/>, so each
/// twin takes the array and the count and calls the span function on <c>sites[..count]</c>. This table is the one
/// place that maps them, read by the mirror test and by the GLSL fixture. The draws are the cases both run.
/// </summary>
internal static class SiteArrays
{
/// <summary>The shader's default CULTMATH_MAX_SITES; the device families spell their array with it.</summary>
internal const int Capacity = 128;
internal static float4 power_border(float3 p, float4[] sites, int count) => math.power_border(p, sites.AsSpan(0, count));
internal static float lattice_cell_distance(
float2 q, float2 cell, float time, float drift, float4[] bodies, int count, float cellSize, float boost, float falloff,
float2 normal, float stretch) =>
math.lattice_cell_distance(q, cell, time, drift, bodies.AsSpan(0, count), cellSize, boost, falloff, normal, stretch);
internal static float4 weighted_lattice_cells(
float2 p, float time, float4[] bodies, int count, float cellSize, float drift, float boost, float falloff,
float smoothing, float2 stretchNormal, float stretch) =>
math.weighted_lattice_cells(p, time, bodies.AsSpan(0, count), cellSize, drift, boost, falloff, smoothing, stretchNormal, stretch);
// The array the shader receives: the sites, then filler a loop running past the count would read. The filler is
// finite and large, so a loop bound that ignores the count changes the result.
private static float4[] Padded(IReadOnlyList<float4> sites)
{
var array = new float4[Capacity];
for (var i = 0; i < Capacity; i++)
array[i] = i < sites.Count ? sites[i] : new float4(40.0f * (i + 1), -30.0f * (i + 1), 7.0f, 90.0f + i);
return array;
}
private static float Uniform(System.Random r, float lo, float hi) => lo + (hi - lo) * r.NextSingle();
// Counts 0, 1, 6, 16 and the capacity are drawn often; the rest of [0, Capacity] fills between.
private static int Count(System.Random r) => r.Next(3) == 0 ? r.Next(Capacity + 1) : new[] { 0, 1, 6, 16, Capacity }[r.Next(5)];
// Bodies on slow orbits (float4(position.xy, 0, sigma)), spread over the plane the lattice cells cover.
private static float4[] Orbit(System.Random r, int count, float time)
{
var bodies = new List<float4>();
for (var i = 0; i < count; i++)
{
var angle = 0.001f * (i + 1) * time + i;
var radius = 100.0f + 2400.0f * r.NextSingle();
bodies.Add(new float4(radius * MathF.Cos(angle), radius * MathF.Sin(angle), 0.0f, Uniform(r, 20.0f, 300.0f)));
}
return Padded(bodies);
}
internal static object[] PowerBorderArguments(System.Random r)
{
var count = Count(r);
var sites = new List<float4>();
for (var i = 0; i < count; i++)
sites.Add(new float4(Uniform(r, -1500.0f, 1500.0f), Uniform(r, -1500.0f, 1500.0f), Uniform(r, -200.0f, 200.0f), Uniform(r, 10.0f, 300.0f)));
return new object[] { new float3(Uniform(r, -1500.0f, 1500.0f), Uniform(r, -1500.0f, 1500.0f), Uniform(r, -200.0f, 200.0f)), Padded(sites), count };
}
internal static object[] LatticeCellDistanceArguments(System.Random r)
{
var angle = Uniform(r, 0.0f, 6.2831855f);
var look = LatticeCellsTests.Looks[r.Next(LatticeCellsTests.Looks.Length)];
var cell = new float2(r.Next(-300, 300), r.Next(-300, 300));
var count = Count(r);
return new object[]
{
new float2(Uniform(r, -1500.0f, 1500.0f), Uniform(r, -1500.0f, 1500.0f)) / LatticeCellsTests.Cell, cell, Uniform(r, 0.0f, 200.0f), LatticeCellsTests.Drift,
Orbit(r, count, Uniform(r, 0.0f, 200.0f)), count, LatticeCellsTests.Cell, look.Boost, look.Falloff, new float2(MathF.Cos(angle), MathF.Sin(angle)), look.Stretch,
};
}
// The unit-less normals the function normalises itself: ordinary, 3-4-5, tiny, huge, and zero (stretch 1 only).
private static readonly float2[] Normals = { LatticeCellsTests.Normal, new(3.0f, 4.0f), new(3.0e-30f, -4.0e-30f), new(1.0e30f, 2.0e30f), new(0.0f, 0.0f) };
internal static readonly LatticeCellsTests.Look WidestLook = new(LatticeMaxBoost, LatticeMaxStretch, LatticeMaxSmoothing, 1.0e6f);
/// <summary>
/// Points whose ring search runs to ring 8 or past it under the widest look (boost, stretch and smoothing at their
/// maxima, falloff so wide that every cell is boosted), found once by the radius the exit rule visits, and the
/// ring-17 point of LatticeCellsTests.TheCapIsReachedAndMatchesTheFullBox first. Each is (p, time).
/// </summary>
internal static readonly IReadOnlyList<(float2 P, float Time, int Ring)> FarPoints = FindFarPoints();
private static List<(float2, float, int)> FindFarPoints()
{
var found = new List<(float2, float, int)>();
var cap = (new float2(1446.7345f, -680.7637f), 97.69269f);
found.Add((cap.Item1, cap.Item2, LatticeCellsTests.Radius(cap.Item1, cap.Item2, LatticeCellsTests.Bodies(cap.Item2), WidestLook, LatticeCellsTests.Rules[0].stop, new Dictionary<(int, int), float>())));
var r = new System.Random(0x1A77);
while (found.Count < 12)
{
var p = LatticeCellsTests.RandomPoint(r);
var time = 13.0f * r.Next(20);
var ring = LatticeCellsTests.Radius(p, time, LatticeCellsTests.Bodies(time), WidestLook, LatticeCellsTests.Rules[0].stop, new Dictionary<(int, int), float>());
if (ring >= 8)
found.Add((p, time, ring));
}
return found;
}
/// <summary>
/// Points where an exit one ring early (the rule "bound 0.5*k") returns another result than the exact search, found once by
/// running both on the six orbiting bodies under each look: the cases that pin the ring exit to the bit.
/// </summary>
internal static readonly IReadOnlyList<(float2 P, float Time, LatticeCellsTests.Look Look)> ExitBoundPoints = FindExitBoundPoints();
private static List<(float2, float, LatticeCellsTests.Look)> FindExitBoundPoints()
{
var found = new List<(float2, float, LatticeCellsTests.Look)>();
var looks = LatticeCellsTests.Looks.Append(WidestLook).ToArray();
var r = new System.Random(0xE517);
while (found.Count < 24)
{
var p = LatticeCellsTests.RandomPoint(r);
var time = 13.0f * r.Next(20);
var look = looks[r.Next(looks.Length)];
var bodies = LatticeCellsTests.Bodies(time);
var early = LatticeCellsTests.Radius(p, time, bodies, look, LatticeCellsTests.Rules[2].stop, new Dictionary<(int, int), float>());
if (early >= LatticeCellsTests.Radius(p, time, bodies, look, LatticeCellsTests.Rules[0].stop, new Dictionary<(int, int), float>()))
continue;
if (!LatticeCellsTests.BoxFold(p, time, bodies, look, Math.Max(0, early), new Dictionary<(int, int), float>()).Equals(LatticeCellsTests.Call(p, time, bodies, look)))
found.Add((p, time, look));
}
return found;
}
internal static object[] LatticeArguments(System.Random r)
{
switch (r.Next(4))
{
case 0:
var far = FarPoints[r.Next(FarPoints.Count)];
return Lattice(far.P, far.Time, Padded(LatticeCellsTests.Bodies(far.Time)), 6, WidestLook, LatticeCellsTests.Normal);
case 1:
var tight = ExitBoundPoints[r.Next(ExitBoundPoints.Count)];
return Lattice(tight.P, tight.Time, Padded(LatticeCellsTests.Bodies(tight.Time)), 6, tight.Look, LatticeCellsTests.Normal);
}
var time = 13.0f * r.Next(20);
var normal = Normals[r.Next(Normals.Length)];
var look = normal.x == 0.0f ? LatticeCellsTests.Looks[0] : LatticeCellsTests.Looks[r.Next(LatticeCellsTests.Looks.Length)];
var count = Count(r);
return Lattice(LatticeCellsTests.RandomPoint(r), time, Orbit(r, count, time), count, look, normal);
}
private static object[] Lattice(float2 p, float time, float4[] bodies, int count, LatticeCellsTests.Look look, float2 normal) =>
new object[] { p, time, bodies, count, LatticeCellsTests.Cell, LatticeCellsTests.Drift,
look.Boost, look.Falloff, look.Smoothing, normal, look.Stretch };
}

View file

@ -20,14 +20,14 @@ lavapipe-glsl 15 vec2 cultmath_iv_min(vec2, vec2) ulp-bounded 256 0 0 a324b35cb4
lavapipe-glsl 16 vec2 cultmath_iv_max(vec2, vec2) ulp-bounded 256 0 0 d68412262ca234c1da130fc5217ba6856da3e92e435c28c1fbdbf3692a89e1fa lavapipe-glsl 16 vec2 cultmath_iv_max(vec2, vec2) ulp-bounded 256 0 0 d68412262ca234c1da130fc5217ba6856da3e92e435c28c1fbdbf3692a89e1fa
lavapipe-glsl 17 vec2 cultmath_iv_sqr(vec2) ulp-bounded 256 0 0 dbbb0e0df015456393452377d689732428a9a8e24d8e2b407ee9620593b4c46b lavapipe-glsl 17 vec2 cultmath_iv_sqr(vec2) ulp-bounded 256 0 0 dbbb0e0df015456393452377d689732428a9a8e24d8e2b407ee9620593b4c46b
lavapipe-glsl 18 vec2 cultmath_iv_sqrt(vec2) ulp-bounded 256 0 0 95bd15d1b283713fc632ced350f5da1b89f8f73e6618329773db1b52acd29991 lavapipe-glsl 18 vec2 cultmath_iv_sqrt(vec2) ulp-bounded 256 0 0 95bd15d1b283713fc632ced350f5da1b89f8f73e6618329773db1b52acd29991
lavapipe-glsl 19 vec2 cultmath_iv_clamp(vec2, float, float) ulp-bounded 256 0 0 268d61fd6d122e2b85e211c96daf410fb7e2d8ff83ec2082709f640504c852a7 lavapipe-glsl 19 vec2 cultmath_iv_clamp(vec2, float, float) ulp-bounded 256 0 0 abe9daf24930a5e3a49ec994c4cae3a0c27b9d019d6d93ec1963ec3daad664a0
lavapipe-glsl 20 vec2 cultmath_iv_saturate(vec2) ulp-bounded 256 0 0 0f844ef8c4d9a9f748c80677bbbfc36b5f6e682eb87c2d46e7e20635d5f55c26 lavapipe-glsl 20 vec2 cultmath_iv_saturate(vec2) ulp-bounded 256 0 0 0f844ef8c4d9a9f748c80677bbbfc36b5f6e682eb87c2d46e7e20635d5f55c26
lavapipe-glsl 21 vec2 cultmath_iv_smoothstep(float, float, vec2) ulp-bounded 256 0 0 3d5944a4c43feb91d6786924e0f5439c05285d28ff1f500b5582893758dd4ce5 lavapipe-glsl 21 vec2 cultmath_iv_smoothstep(float, float, vec2) ulp-bounded 256 0 0 3d5944a4c43feb91d6786924e0f5439c05285d28ff1f500b5582893758dd4ce5
lavapipe-glsl 22 vec2 cultmath_iv_lerp(vec2, vec2, float) ulp-bounded 256 0 0 96b09e90a3fbbfe81379a29a23b676e490bc7dc6bb731450197707e6cf4c573f lavapipe-glsl 22 vec2 cultmath_iv_lerp(vec2, vec2, float) ulp-bounded 256 0 0 96b09e90a3fbbfe81379a29a23b676e490bc7dc6bb731450197707e6cf4c573f
lavapipe-glsl 23 vec2 cultmath_iv_snoise_ball(vec3, float) ulp-bounded 242 54 0 570eb031260c8f4aab42edc57a5fa1d0da70e8a4e6f0b4bf68208c15506f7cb5 lavapipe-glsl 23 vec2 cultmath_iv_snoise_ball(vec3, float) ulp-bounded 242 54 0 570eb031260c8f4aab42edc57a5fa1d0da70e8a4e6f0b4bf68208c15506f7cb5
lavapipe-glsl 24 vec2 cultmath_iv_fbm_ball(vec3, float, int, float, float) ulp-bounded 250 56 0 9e7017426314537f84c58dcdd1f85188cbe8f4dd9ababed887f60776cf861ebf lavapipe-glsl 24 vec2 cultmath_iv_fbm_ball(vec3, float, int, float, float) ulp-bounded 250 56 0 9e7017426314537f84c58dcdd1f85188cbe8f4dd9ababed887f60776cf861ebf
lavapipe-glsl 25 vec4 cultmath_iv_frustum_ball(vec2, float, float, float, float) ulp-bounded 198 1.6457063576593662 0 327c306aa4d06aecbb2681d8808c6dbf0567e87b54a6067c68001e36b3e52169 lavapipe-glsl 25 vec4 cultmath_iv_frustum_ball(vec2, float, float, float, float) ulp-bounded 198 1.6457063576593662 0 327c306aa4d06aecbb2681d8808c6dbf0567e87b54a6067c68001e36b3e52169
lavapipe-glsl 26 vec2 cultmath_iv_exp(vec2) ulp-bounded, platform exp 7 7.526057804152758 0 1a46b0fa116f55e5abd70ec9f2a66de0befe8b3577fe472f9eaa98932aae8040 lavapipe-glsl 26 vec2 cultmath_iv_exp(vec2) ulp-bounded, platform exp 7 7.526057804152758 0 b4af8232dc3dd782cc6d790e928ede0f6ba6dfdffd6c71dd9494fb58973e485c
lavapipe-glsl 27 CultPhasor cultmath_phacelle(vec3, vec3, float, float) ulp-bounded, platform exp, sin, cos; across platforms |diff| <= 2^-20 max(|v|, 1); on a device |diff| <= 2^-18 max(|v|, 1) 0 18.5 0 705b4c18027a0838218645e9368c3749ae4b5bf75e0485985725e8e72b73ab88 lavapipe-glsl 27 CultPhasor cultmath_phacelle(vec3, vec3, float, float) ulp-bounded, platform exp, sin, cos; across platforms |diff| <= 2^-20 max(|v|, 1); on a device |diff| <= 2^-18 max(|v|, 1) 0 18.5 0 705b4c18027a0838218645e9368c3749ae4b5bf75e0485985725e8e72b73ab88
lavapipe-glsl 28 float cultmath_snoise(vec3) ulp-bounded, on the simplex tie set 127 112 0 6929326e4f27726d7bbdbac3a7823209e2ecfcd301018ef164527c4c1a9f7ae8 lavapipe-glsl 28 float cultmath_snoise(vec3) ulp-bounded, on the simplex tie set 127 112 0 6929326e4f27726d7bbdbac3a7823209e2ecfcd301018ef164527c4c1a9f7ae8
lavapipe-glsl 29 vec4 cultmath_snoise_grad(vec3) ulp-bounded, on the simplex tie set 102 546.5 0 6173a812fd4059fa19bce84d34858c6035cdda98aa21a2b210e611cbd58c29d6 lavapipe-glsl 29 vec4 cultmath_snoise_grad(vec3) ulp-bounded, on the simplex tie set 102 546.5 0 6173a812fd4059fa19bce84d34858c6035cdda98aa21a2b210e611cbd58c29d6
@ -36,7 +36,7 @@ lavapipe-glsl 31 vec3 cultmath_af_symbol(float, float) ulp-bounded 256 0 0 04147
lavapipe-glsl 32 vec3 cultmath_af_from_iv(vec2) ulp-bounded 256 0 0 306fe58a3268199fc8fd845a32d956f349a790422fa5c87d5a0824636cb55424 lavapipe-glsl 32 vec3 cultmath_af_from_iv(vec2) ulp-bounded 256 0 0 306fe58a3268199fc8fd845a32d956f349a790422fa5c87d5a0824636cb55424
lavapipe-glsl 33 vec2 cultmath_af_range(vec3) ulp-bounded 256 0 0 6245eeba19473d69d0d8155e20e2e6089a6843140723d23ed5d501064023a44a lavapipe-glsl 33 vec2 cultmath_af_range(vec3) ulp-bounded 256 0 0 6245eeba19473d69d0d8155e20e2e6089a6843140723d23ed5d501064023a44a
lavapipe-glsl 34 vec3 cultmath_af_add(vec3, vec3) ulp-bounded 256 0 0 cb1cb3fc69224837342de767525d332687b9d3e17b3de255c28e2f0f65f32640 lavapipe-glsl 34 vec3 cultmath_af_add(vec3, vec3) ulp-bounded 256 0 0 cb1cb3fc69224837342de767525d332687b9d3e17b3de255c28e2f0f65f32640
lavapipe-glsl 35 vec3 cultmath_af_sub(vec3, vec3) ulp-bounded 256 0 0 df44c501a8be20dc46a54faac9583da13f4367adc501a33fe14624f10e54b558 lavapipe-glsl 35 vec3 cultmath_af_sub(vec3, vec3) ulp-bounded 256 0 0 a8fad8ea86cf61841430293fdd9840a54008160250114040380c9634c63bb17e
lavapipe-glsl 36 vec3 cultmath_af_neg(vec3) ulp-bounded 256 0 0 e485acf8ee21c6d880621cc6216b3b2a78318f56f5864b33c5509be27e22a694 lavapipe-glsl 36 vec3 cultmath_af_neg(vec3) ulp-bounded 256 0 0 e485acf8ee21c6d880621cc6216b3b2a78318f56f5864b33c5509be27e22a694
lavapipe-glsl 37 vec3 cultmath_af_scale(vec3, float) ulp-bounded 256 0 0 8b0c8a540fea82fe058efc42ffa38dbd69850e579cefe5fdbc4c164b715ec15d lavapipe-glsl 37 vec3 cultmath_af_scale(vec3, float) ulp-bounded 256 0 0 8b0c8a540fea82fe058efc42ffa38dbd69850e579cefe5fdbc4c164b715ec15d
lavapipe-glsl 38 vec3 cultmath_af_add_iv(vec3, vec2) ulp-bounded 256 0 0 620f747e6ba53014c9714a8dbfaa3ff93f281c80938bd035ef64657e3bdc1a16 lavapipe-glsl 38 vec3 cultmath_af_add_iv(vec3, vec2) ulp-bounded 256 0 0 620f747e6ba53014c9714a8dbfaa3ff93f281c80938bd035ef64657e3bdc1a16
@ -50,7 +50,7 @@ lavapipe-glsl 45 vec3 cultmath_af_snoise(vec3, vec3, float) ulp-bounded, reach 1
lavapipe-glsl 46 vec3 cultmath_af_fbm(vec3, vec3, float, int, float, float) ulp-bounded, reach 1e-6 to 1e-3 27 102.5 0 42246db4e19a1da20433517cdc7016dda375ea70dc90d1a777679bfd76fb59e8 lavapipe-glsl 46 vec3 cultmath_af_fbm(vec3, vec3, float, int, float, float) ulp-bounded, reach 1e-6 to 1e-3 27 102.5 0 42246db4e19a1da20433517cdc7016dda375ea70dc90d1a777679bfd76fb59e8
lavapipe-glsl 47 float cultmath_cycles_divide(float, float) ulp-bounded 256 0 0 87f143c57806ad52c3295cde67e97f31e39c8572b8c0a4b8c577e6fcd8ecbebf lavapipe-glsl 47 float cultmath_cycles_divide(float, float) ulp-bounded 256 0 0 87f143c57806ad52c3295cde67e97f31e39c8572b8c0a4b8c577e6fcd8ecbebf
lavapipe-glsl 48 float cultmath_cycles_power(float, float) ulp-bounded, platform pow 136 4.918926113980705 0 0e62db5ea32fdb9fdd3a9b135a914ddadd3231bdee0ff15c709102b26bb89186 lavapipe-glsl 48 float cultmath_cycles_power(float, float) ulp-bounded, platform pow 136 4.918926113980705 0 0e62db5ea32fdb9fdd3a9b135a914ddadd3231bdee0ff15c709102b26bb89186
lavapipe-glsl 49 float cultmath_cycles_logarithm(float, float) ulp-bounded, platform log 147 4.94622778071426 0 5b2bf1411512c7e5a3f4b335a16fc3ba00ba0784bb87acb14b7d1b9dd551b2f3 lavapipe-glsl 49 float cultmath_cycles_logarithm(float, float) ulp-bounded, platform log 146 4.94622778071426 0 5b2bf1411512c7e5a3f4b335a16fc3ba00ba0784bb87acb14b7d1b9dd551b2f3
lavapipe-glsl 50 float cultmath_cycles_sqrt(float) ulp-bounded 256 0 0 6d4beb34e93ae304b3d9d30f203fb6c2ba222affc0efc35bb5441528e9062a9a lavapipe-glsl 50 float cultmath_cycles_sqrt(float) ulp-bounded 256 0 0 6d4beb34e93ae304b3d9d30f203fb6c2ba222affc0efc35bb5441528e9062a9a
lavapipe-glsl 51 float cultmath_cycles_inverse_sqrt(float) ulp-bounded 256 0 0 71aadd5bd384b9ca2eb89827de53af810a1350b02177de1987e9405e4a6e9ad8 lavapipe-glsl 51 float cultmath_cycles_inverse_sqrt(float) ulp-bounded 256 0 0 71aadd5bd384b9ca2eb89827de53af810a1350b02177de1987e9405e4a6e9ad8
lavapipe-glsl 52 float cultmath_cycles_modulo(float, float) ulp-bounded 256 0 0 cf055cafcb5b2d4789a0a2d5cfbc8bc79f52df646b60f5e47db3f994680949e9 lavapipe-glsl 52 float cultmath_cycles_modulo(float, float) ulp-bounded 256 0 0 cf055cafcb5b2d4789a0a2d5cfbc8bc79f52df646b60f5e47db3f994680949e9
@ -96,16 +96,16 @@ lavapipe-glsl 91 vec3 cultmath_cycles_mix_val(float, vec3, vec3) ulp-bounded 256
lavapipe-glsl 92 vec3 cultmath_cycles_mix_color(float, vec3, vec3) ulp-bounded 256 0 0 00067b1567bcd6c07a81fa5002f145dfca22437a3f3df5389b2665334d14909b lavapipe-glsl 92 vec3 cultmath_cycles_mix_color(float, vec3, vec3) ulp-bounded 256 0 0 00067b1567bcd6c07a81fa5002f145dfca22437a3f3df5389b2665334d14909b
lavapipe-glsl 93 vec3 cultmath_cycles_mix_soft(float, vec3, vec3) ulp-bounded 157 1.9750388068880254 0 ed7c5508ca0543cc20a5f0c5624e75e3ca2a2d6044d481816eeda2edc50c0592 lavapipe-glsl 93 vec3 cultmath_cycles_mix_soft(float, vec3, vec3) ulp-bounded 157 1.9750388068880254 0 ed7c5508ca0543cc20a5f0c5624e75e3ca2a2d6044d481816eeda2edc50c0592
lavapipe-glsl 94 vec3 cultmath_cycles_mix_linear(float, vec3, vec3) ulp-bounded 256 0 0 672435cc30bcbbb253cce7316ee0712b1a18f06924bc89cea2d3388dd164a15d lavapipe-glsl 94 vec3 cultmath_cycles_mix_linear(float, vec3, vec3) ulp-bounded 256 0 0 672435cc30bcbbb253cce7316ee0712b1a18f06924bc89cea2d3388dd164a15d
lavapipe-glsl 95 float cultmath_cycles_arcsine(float) ulp-bounded, platform asin 155 2734.286809678319 0 f28f3ed0a53ffeebdfa84865d2d3d41590e6b46fc1578d40e1215e2a9e1af585 lavapipe-glsl 95 float cultmath_cycles_arcsine(float) ulp-bounded, platform asin 154 2734.286809678319 0 f28f3ed0a53ffeebdfa84865d2d3d41590e6b46fc1578d40e1215e2a9e1af585
lavapipe-glsl 96 float cultmath_cycles_arccosine(float) ulp-bounded, platform acos 79 1046.8674409774287 0 22a2c38ab5a232cdbb33cf0c133362581a398f64a4d0d5d6dd89dc5af06bd890 lavapipe-glsl 96 float cultmath_cycles_arccosine(float) ulp-bounded, platform acos 79 1046.8674409774287 0 22a2c38ab5a232cdbb33cf0c133362581a398f64a4d0d5d6dd89dc5af06bd890
lavapipe-glsl 97 float cultmath_cycles_arctan2(float, float) ulp-bounded, platform atan 7 25.5 0 2c60447d80186f6dc9726f19c09fa96ec3f66d8f5688968da533d9195ea463c4 lavapipe-glsl 97 float cultmath_cycles_arctan2(float, float) ulp-bounded, platform atan 6 25.5 0 2c60447d80186f6dc9726f19c09fa96ec3f66d8f5688968da533d9195ea463c4
lavapipe-glsl 98 mat3 cultmath_snoise_hessian(vec3) ulp-bounded 30 1358.4375 0 62bbafb8420ed5aaa0ce11d1470ca98945e690b186c33a1e7cf4d71478396465 lavapipe-glsl 98 mat3 cultmath_snoise_hessian(vec3) ulp-bounded 30 1358.4375 0 62bbafb8420ed5aaa0ce11d1470ca98945e690b186c33a1e7cf4d71478396465
lavapipe-glsl 99 mat3 cultmath_snoise_hessian(vec3) ulp-bounded, on the simplex tie set 110 1317.4754224467295 0 0efd9b3a637b46033ed0351f64de274db4d9ce05bb83dd1ce57afb3ceaea26bc lavapipe-glsl 99 mat3 cultmath_snoise_hessian(vec3) ulp-bounded, on the simplex tie set 110 1317.4754224467295 0 0efd9b3a637b46033ed0351f64de274db4d9ce05bb83dd1ce57afb3ceaea26bc
lavapipe-glsl 100 float cultmath_integral_clamp_linear(float, float, float, float, float, float) ulp-bounded 256 0 0 84bf9c9a2126b3114a692507cc54f58d313afc33de8a1cac83969cc508433cbe lavapipe-glsl 100 float cultmath_integral_clamp_linear(float, float, float, float, float, float) ulp-bounded 256 0 0 84bf9c9a2126b3114a692507cc54f58d313afc33de8a1cac83969cc508433cbe
lavapipe-glsl 101 float cultmath_integral_clamp_linear(float, float, float, float, float, float) ulp-bounded, lo above hi and infinite bounds 256 0 0 ff999094d7794aa525e0f9f9a067aa21408a59559aa546cace3b1bb4a8c7f96d lavapipe-glsl 101 float cultmath_integral_clamp_linear(float, float, float, float, float, float) ulp-bounded, lo above hi and infinite bounds 256 0 0 ff999094d7794aa525e0f9f9a067aa21408a59559aa546cace3b1bb4a8c7f96d
lavapipe-glsl 102 float cultmath_integral_clamp_linear(float, float, float, float, float, float) ulp-bounded, zero-length spans and flat infinite clamps 256 0 0 c3cc48f4edcac6801b9223f12d289934916431bdfaea8f68af33a1b833ab9860 lavapipe-glsl 102 float cultmath_integral_clamp_linear(float, float, float, float, float, float) ulp-bounded, zero-length spans and flat infinite clamps 256 0 0 c3cc48f4edcac6801b9223f12d289934916431bdfaea8f68af33a1b833ab9860
lavapipe-glsl 103 SpanNoise cultmath_span_fbm(SpanPath, SpanStage, int, float, float) ulp-bounded 7 1012477.3459086476 0 97c531c3a690e056d95e949e243ae74111d4a93a39fd22c0358a103b3bb14bc1 lavapipe-glsl 103 SpanNoise cultmath_span_fbm(SpanPath, SpanStage, int, float, float) ulp-bounded 7 1012477.3459086476 0 97c531c3a690e056d95e949e243ae74111d4a93a39fd22c0358a103b3bb14bc1
lavapipe-glsl 104 SpanNoise cultmath_span_ridged(SpanPath, SpanStage, int, float, float) ulp-bounded 8 948534.9108393334 0 c5d55a1d05403f6a1a7b7946c3a4c9556277f217fc660d338b3894efbb333e43 lavapipe-glsl 104 SpanNoise cultmath_span_ridged(SpanPath, SpanStage, int, float, float) ulp-bounded 8 948534.9108393334 0 54fd229d69439ff3318690b7802143f8410dc7ece30ad0d56ccc4b68bf39bec2
lavapipe-glsl 105 vec4 cultmath_snoise_grad(vec3) ulp-bounded, coordinate bands 1e-4 to 1e2 116 249.5 0 fff9658a8ec055766c9327ee9b680cf7fb83736c472ae90a11e50d6d2a053fdc lavapipe-glsl 105 vec4 cultmath_snoise_grad(vec3) ulp-bounded, coordinate bands 1e-4 to 1e2 116 249.5 0 fff9658a8ec055766c9327ee9b680cf7fb83736c472ae90a11e50d6d2a053fdc
lavapipe-glsl 106 vec4 cultmath_snoise_grad(vec3) ulp-bounded, coordinate bands 1e2 to 4e6 70 11866336.625 0 833484714ebcea59d86e0fbc2eaa4ae247c29541703763989e6758547dbf6db8 lavapipe-glsl 106 vec4 cultmath_snoise_grad(vec3) ulp-bounded, coordinate bands 1e2 to 4e6 70 11866336.625 0 833484714ebcea59d86e0fbc2eaa4ae247c29541703763989e6758547dbf6db8
lavapipe-glsl 107 mat3 cultmath_snoise_hessian(vec3) ulp-bounded, coordinate bands 1e-4 to 1e2 120 980.7417469278579 0 3ea43dabe0091ea8e994c1dfc65c7274ed38b9343d4d1d06f7caa09c806c91df lavapipe-glsl 107 mat3 cultmath_snoise_hessian(vec3) ulp-bounded, coordinate bands 1e-4 to 1e2 120 980.7417469278579 0 3ea43dabe0091ea8e994c1dfc65c7274ed38b9343d4d1d06f7caa09c806c91df
@ -117,13 +117,15 @@ lavapipe-glsl 112 vec3 cultmath_segment_closest(vec3, vec3, vec3) ulp-bounded 21
lavapipe-glsl 113 vec4 cultmath_power_border_step(vec3, vec4, vec4, vec4) ulp-bounded 214 43 0 fdfdff73434a146489b002af5e80663658b2dd7b86105a7e65bdab755ab54a5e lavapipe-glsl 113 vec4 cultmath_power_border_step(vec3, vec4, vec4, vec4) ulp-bounded 214 43 0 fdfdff73434a146489b002af5e80663658b2dd7b86105a7e65bdab755ab54a5e
lavapipe-glsl 114 vec4 cultmath_power_score(vec3, vec4) ulp-bounded 207 1.4619722193127032 0 dc1aa639ae1dc44003a0f5d8c7c99b2e3d7f51b158c96b3c3bd7a9f7df6d927e lavapipe-glsl 114 vec4 cultmath_power_score(vec3, vec4) ulp-bounded 207 1.4619722193127032 0 dc1aa639ae1dc44003a0f5d8c7c99b2e3d7f51b158c96b3c3bd7a9f7df6d927e
lavapipe-glsl 115 vec4 cultmath_multiplicative_score(vec3, vec4) ulp-bounded 234 1.5529504197086745 0 ee1af26ac82489e8d363e7a6b6b59da7dacfb397d4bc9a723dd68607a69da41a lavapipe-glsl 115 vec4 cultmath_multiplicative_score(vec3, vec4) ulp-bounded 234 1.5529504197086745 0 ee1af26ac82489e8d363e7a6b6b59da7dacfb397d4bc9a723dd68607a69da41a
lavapipe-glsl 116 CultSiteCells cultmath_site_cells_insert(CultSiteCells, vec4, int) ulp-bounded 256 0 0 b8687b139aaa865e9f28fbc84fe37dd4a1d4873d5ff8e2074922c07f56551576 lavapipe-glsl 116 CultSiteCells cultmath_site_cells_insert(CultSiteCells, vec4, int) ulp-bounded 256 0 0 bfad8b369011cfbf14644fec68ce65620694fcbf3cd3804f54953d55587313fc
lavapipe-glsl 117 vec4 cultmath_site_cells_border(CultSiteCells) ulp-bounded 228 1.069496760346883 0 d7b478bef7e6f80902d0b5b255cb4fca30757ff6b9d25dac2030b037a8d342ce lavapipe-glsl 117 vec4 cultmath_site_cells_border(CultSiteCells) ulp-bounded 228 1.069496760346883 0 d7b478bef7e6f80902d0b5b255cb4fca30757ff6b9d25dac2030b037a8d342ce
lavapipe-glsl 118 float cultmath_gabriel_margin(vec3, vec3, vec3) ulp-bounded 201 2 0 6a80fbef486de496a08b7c8e7f9f65e2cc0f5b9f93abe50c43e84ad88d1ed034 lavapipe-glsl 118 float cultmath_gabriel_margin(vec3, vec3, vec3) ulp-bounded 201 2 0 6a80fbef486de496a08b7c8e7f9f65e2cc0f5b9f93abe50c43e84ad88d1ed034
lavapipe-glsl 119 vec3 cultmath_lattice_site(vec2, float, float) ulp-bounded 256 0 0 84df739c336796638dcf0a99c328a4b8c150c25d80e02248d4b6d023dc13161f lavapipe-glsl 119 vec3 cultmath_lattice_site(vec2, float, float) ulp-bounded 256 0 0 84df739c336796638dcf0a99c328a4b8c150c25d80e02248d4b6d023dc13161f
lavapipe-glsl 120 float cultmath_lattice_weight(float, float, float, float) ulp-bounded 231 2.0671110200639147 0 434cdf5603abc2942aa624b9d2e98d5f1231706c86180a80439bc8b72bdd066e lavapipe-glsl 120 float cultmath_lattice_weight(float, float, float, float) ulp-bounded 231 2.0671110200639147 0 434cdf5603abc2942aa624b9d2e98d5f1231706c86180a80439bc8b72bdd066e
lavapipe-glsl 121 float cultmath_lattice_distance(vec2, vec2, float, vec2, float) ulp-bounded 256 0 0 622d0e1ea0fcf0e2700558dbdaea92b8ce02e44048dbf35944c23b5088c3e40c lavapipe-glsl 121 float cultmath_lattice_distance(vec2, vec2, float, vec2, float) ulp-bounded 256 0 0 ba3fa3bdb4f5f5b2478a4ddfb2e08f2f425e997778a716884e43b13d6ed050b5
lavapipe-glsl 122 float cultmath_lattice_smooth_step(float, float, float, float) ulp-bounded 256 0 0 e6c9aeb52c5e63ebf3c8525dd39b5b88d5f890fd4f23f4894a92232807a3d94e lavapipe-glsl 122 float cultmath_lattice_smooth_step(float, float, float, float) ulp-bounded 256 0 0 6cbeddde536b12e83873f2e5ac60d16bef22049d2a7dea61af0ff1e0d5b84740
lavapipe-glsl 123 vec4 cultmath_power_border(vec3, vec4[128], int) ulp-bounded 188 65.72121683891783 0 8427c2b99109a4f59ff745fdb87f261deb1cbae34ce99bc1ae05ebb7f9963b4d
lavapipe-glsl 124 vec4 cultmath_weighted_lattice_cells(vec2, float, vec4[128], int, float, float, float, float, float, vec2, float) ulp-bounded 179 3 0 fd3b58c4e2ba8a7bf801a1e6cdff82468abcb368442bc98b2583fda9d2e6f823
lavapipe-hlsl 0 float cultmath_snoise(vec3) ulp-bounded 94 122.6875 0 5d41c14241a13124f2e1b49f230f5df0c48d0d69fb1be9ef2f33dc1702bf0b0a lavapipe-hlsl 0 float cultmath_snoise(vec3) ulp-bounded 94 122.6875 0 5d41c14241a13124f2e1b49f230f5df0c48d0d69fb1be9ef2f33dc1702bf0b0a
lavapipe-hlsl 1 float cultmath_snoise(vec2) ulp-bounded 204 1 0 81d9c62726afdccbbe4ffef173b5c92731eb6bf5927b3979d653e50dbd5d4064 lavapipe-hlsl 1 float cultmath_snoise(vec2) ulp-bounded 204 1 0 81d9c62726afdccbbe4ffef173b5c92731eb6bf5927b3979d653e50dbd5d4064
lavapipe-hlsl 2 vec4 cultmath_snoise_grad(vec3) ulp-bounded 71 617.5 0 f7e9ed1707cdb015efaba0e0150193309da0d4c125349518f8b7c60a17b25790 lavapipe-hlsl 2 vec4 cultmath_snoise_grad(vec3) ulp-bounded 71 617.5 0 f7e9ed1707cdb015efaba0e0150193309da0d4c125349518f8b7c60a17b25790
@ -143,14 +145,14 @@ lavapipe-hlsl 15 vec2 cultmath_iv_min(vec2, vec2) ulp-bounded 256 0 0 a324b35cb4
lavapipe-hlsl 16 vec2 cultmath_iv_max(vec2, vec2) ulp-bounded 256 0 0 d68412262ca234c1da130fc5217ba6856da3e92e435c28c1fbdbf3692a89e1fa lavapipe-hlsl 16 vec2 cultmath_iv_max(vec2, vec2) ulp-bounded 256 0 0 d68412262ca234c1da130fc5217ba6856da3e92e435c28c1fbdbf3692a89e1fa
lavapipe-hlsl 17 vec2 cultmath_iv_sqr(vec2) ulp-bounded 256 0 0 dbbb0e0df015456393452377d689732428a9a8e24d8e2b407ee9620593b4c46b lavapipe-hlsl 17 vec2 cultmath_iv_sqr(vec2) ulp-bounded 256 0 0 dbbb0e0df015456393452377d689732428a9a8e24d8e2b407ee9620593b4c46b
lavapipe-hlsl 18 vec2 cultmath_iv_sqrt(vec2) ulp-bounded 256 0 0 95bd15d1b283713fc632ced350f5da1b89f8f73e6618329773db1b52acd29991 lavapipe-hlsl 18 vec2 cultmath_iv_sqrt(vec2) ulp-bounded 256 0 0 95bd15d1b283713fc632ced350f5da1b89f8f73e6618329773db1b52acd29991
lavapipe-hlsl 19 vec2 cultmath_iv_clamp(vec2, float, float) ulp-bounded 256 0 0 268d61fd6d122e2b85e211c96daf410fb7e2d8ff83ec2082709f640504c852a7 lavapipe-hlsl 19 vec2 cultmath_iv_clamp(vec2, float, float) ulp-bounded 256 0 0 abe9daf24930a5e3a49ec994c4cae3a0c27b9d019d6d93ec1963ec3daad664a0
lavapipe-hlsl 20 vec2 cultmath_iv_saturate(vec2) ulp-bounded 256 0 0 0f844ef8c4d9a9f748c80677bbbfc36b5f6e682eb87c2d46e7e20635d5f55c26 lavapipe-hlsl 20 vec2 cultmath_iv_saturate(vec2) ulp-bounded 256 0 0 0f844ef8c4d9a9f748c80677bbbfc36b5f6e682eb87c2d46e7e20635d5f55c26
lavapipe-hlsl 21 vec2 cultmath_iv_smoothstep(float, float, vec2) ulp-bounded 256 0 0 3d5944a4c43feb91d6786924e0f5439c05285d28ff1f500b5582893758dd4ce5 lavapipe-hlsl 21 vec2 cultmath_iv_smoothstep(float, float, vec2) ulp-bounded 256 0 0 3d5944a4c43feb91d6786924e0f5439c05285d28ff1f500b5582893758dd4ce5
lavapipe-hlsl 22 vec2 cultmath_iv_lerp(vec2, vec2, float) ulp-bounded 256 0 0 96b09e90a3fbbfe81379a29a23b676e490bc7dc6bb731450197707e6cf4c573f lavapipe-hlsl 22 vec2 cultmath_iv_lerp(vec2, vec2, float) ulp-bounded 256 0 0 96b09e90a3fbbfe81379a29a23b676e490bc7dc6bb731450197707e6cf4c573f
lavapipe-hlsl 23 vec2 cultmath_iv_snoise_ball(vec3, float) ulp-bounded 244 54 0 f36530a4cd8c9fdc1c73e7ddb6782cd11cbf8493e2ee7b0656e818812504267e lavapipe-hlsl 23 vec2 cultmath_iv_snoise_ball(vec3, float) ulp-bounded 244 54 0 f36530a4cd8c9fdc1c73e7ddb6782cd11cbf8493e2ee7b0656e818812504267e
lavapipe-hlsl 24 vec2 cultmath_iv_fbm_ball(vec3, float, int, float, float) ulp-bounded 252 75 0 a1677ea42ce91a5d2b6f83fa7c44879b7a184ed4631108d7a1c6e710bf26ff02 lavapipe-hlsl 24 vec2 cultmath_iv_fbm_ball(vec3, float, int, float, float) ulp-bounded 252 75 0 a1677ea42ce91a5d2b6f83fa7c44879b7a184ed4631108d7a1c6e710bf26ff02
lavapipe-hlsl 25 vec4 cultmath_iv_frustum_ball(vec2, float, float, float, float) ulp-bounded 198 1.6457063576593662 0 327c306aa4d06aecbb2681d8808c6dbf0567e87b54a6067c68001e36b3e52169 lavapipe-hlsl 25 vec4 cultmath_iv_frustum_ball(vec2, float, float, float, float) ulp-bounded 198 1.6457063576593662 0 327c306aa4d06aecbb2681d8808c6dbf0567e87b54a6067c68001e36b3e52169
lavapipe-hlsl 26 vec2 cultmath_iv_exp(vec2) ulp-bounded, platform exp 7 7.526057804152758 0 1a46b0fa116f55e5abd70ec9f2a66de0befe8b3577fe472f9eaa98932aae8040 lavapipe-hlsl 26 vec2 cultmath_iv_exp(vec2) ulp-bounded, platform exp 7 7.526057804152758 0 b4af8232dc3dd782cc6d790e928ede0f6ba6dfdffd6c71dd9494fb58973e485c
lavapipe-hlsl 27 CultPhasor cultmath_phacelle(vec3, vec3, float, float) ulp-bounded, platform exp, sin, cos; across platforms |diff| <= 2^-20 max(|v|, 1); on a device |diff| <= 2^-18 max(|v|, 1) 0 18.5 0 705b4c18027a0838218645e9368c3749ae4b5bf75e0485985725e8e72b73ab88 lavapipe-hlsl 27 CultPhasor cultmath_phacelle(vec3, vec3, float, float) ulp-bounded, platform exp, sin, cos; across platforms |diff| <= 2^-20 max(|v|, 1); on a device |diff| <= 2^-18 max(|v|, 1) 0 18.5 0 705b4c18027a0838218645e9368c3749ae4b5bf75e0485985725e8e72b73ab88
lavapipe-hlsl 28 float cultmath_snoise(vec3) ulp-bounded, on the simplex tie set 144 99 0 be76512b18eb678194dad0c4726c0aef9ba34012b60b79d24c2df27989707145 lavapipe-hlsl 28 float cultmath_snoise(vec3) ulp-bounded, on the simplex tie set 144 99 0 be76512b18eb678194dad0c4726c0aef9ba34012b60b79d24c2df27989707145
lavapipe-hlsl 29 vec4 cultmath_snoise_grad(vec3) ulp-bounded, on the simplex tie set 111 546.5 0 124d4be07cbb66edab644e649c081cb7c8be5d99b6c1d84fec5d7ef65c76ed0f lavapipe-hlsl 29 vec4 cultmath_snoise_grad(vec3) ulp-bounded, on the simplex tie set 111 546.5 0 124d4be07cbb66edab644e649c081cb7c8be5d99b6c1d84fec5d7ef65c76ed0f
@ -159,7 +161,7 @@ lavapipe-hlsl 31 vec3 cultmath_af_symbol(float, float) ulp-bounded 256 0 0 04147
lavapipe-hlsl 32 vec3 cultmath_af_from_iv(vec2) ulp-bounded 256 0 0 306fe58a3268199fc8fd845a32d956f349a790422fa5c87d5a0824636cb55424 lavapipe-hlsl 32 vec3 cultmath_af_from_iv(vec2) ulp-bounded 256 0 0 306fe58a3268199fc8fd845a32d956f349a790422fa5c87d5a0824636cb55424
lavapipe-hlsl 33 vec2 cultmath_af_range(vec3) ulp-bounded 256 0 0 6245eeba19473d69d0d8155e20e2e6089a6843140723d23ed5d501064023a44a lavapipe-hlsl 33 vec2 cultmath_af_range(vec3) ulp-bounded 256 0 0 6245eeba19473d69d0d8155e20e2e6089a6843140723d23ed5d501064023a44a
lavapipe-hlsl 34 vec3 cultmath_af_add(vec3, vec3) ulp-bounded 256 0 0 cb1cb3fc69224837342de767525d332687b9d3e17b3de255c28e2f0f65f32640 lavapipe-hlsl 34 vec3 cultmath_af_add(vec3, vec3) ulp-bounded 256 0 0 cb1cb3fc69224837342de767525d332687b9d3e17b3de255c28e2f0f65f32640
lavapipe-hlsl 35 vec3 cultmath_af_sub(vec3, vec3) ulp-bounded 256 0 0 df44c501a8be20dc46a54faac9583da13f4367adc501a33fe14624f10e54b558 lavapipe-hlsl 35 vec3 cultmath_af_sub(vec3, vec3) ulp-bounded 256 0 0 a8fad8ea86cf61841430293fdd9840a54008160250114040380c9634c63bb17e
lavapipe-hlsl 36 vec3 cultmath_af_neg(vec3) ulp-bounded 256 0 0 e485acf8ee21c6d880621cc6216b3b2a78318f56f5864b33c5509be27e22a694 lavapipe-hlsl 36 vec3 cultmath_af_neg(vec3) ulp-bounded 256 0 0 e485acf8ee21c6d880621cc6216b3b2a78318f56f5864b33c5509be27e22a694
lavapipe-hlsl 37 vec3 cultmath_af_scale(vec3, float) ulp-bounded 256 0 0 8b0c8a540fea82fe058efc42ffa38dbd69850e579cefe5fdbc4c164b715ec15d lavapipe-hlsl 37 vec3 cultmath_af_scale(vec3, float) ulp-bounded 256 0 0 8b0c8a540fea82fe058efc42ffa38dbd69850e579cefe5fdbc4c164b715ec15d
lavapipe-hlsl 38 vec3 cultmath_af_add_iv(vec3, vec2) ulp-bounded 256 0 0 620f747e6ba53014c9714a8dbfaa3ff93f281c80938bd035ef64657e3bdc1a16 lavapipe-hlsl 38 vec3 cultmath_af_add_iv(vec3, vec2) ulp-bounded 256 0 0 620f747e6ba53014c9714a8dbfaa3ff93f281c80938bd035ef64657e3bdc1a16
@ -173,7 +175,7 @@ lavapipe-hlsl 45 vec3 cultmath_af_snoise(vec3, vec3, float) ulp-bounded, reach 1
lavapipe-hlsl 46 vec3 cultmath_af_fbm(vec3, vec3, float, int, float, float) ulp-bounded, reach 1e-6 to 1e-3 24 122.75 0 7f941b1f0c76d9f2121027a555ae7f2a741cc164402f0df75237198ac97f850b lavapipe-hlsl 46 vec3 cultmath_af_fbm(vec3, vec3, float, int, float, float) ulp-bounded, reach 1e-6 to 1e-3 24 122.75 0 7f941b1f0c76d9f2121027a555ae7f2a741cc164402f0df75237198ac97f850b
lavapipe-hlsl 47 float cultmath_cycles_divide(float, float) ulp-bounded 256 0 0 87f143c57806ad52c3295cde67e97f31e39c8572b8c0a4b8c577e6fcd8ecbebf lavapipe-hlsl 47 float cultmath_cycles_divide(float, float) ulp-bounded 256 0 0 87f143c57806ad52c3295cde67e97f31e39c8572b8c0a4b8c577e6fcd8ecbebf
lavapipe-hlsl 48 float cultmath_cycles_power(float, float) ulp-bounded, platform pow 136 4.918926113980705 0 0e62db5ea32fdb9fdd3a9b135a914ddadd3231bdee0ff15c709102b26bb89186 lavapipe-hlsl 48 float cultmath_cycles_power(float, float) ulp-bounded, platform pow 136 4.918926113980705 0 0e62db5ea32fdb9fdd3a9b135a914ddadd3231bdee0ff15c709102b26bb89186
lavapipe-hlsl 49 float cultmath_cycles_logarithm(float, float) ulp-bounded, platform log 147 4.94622778071426 0 5b2bf1411512c7e5a3f4b335a16fc3ba00ba0784bb87acb14b7d1b9dd551b2f3 lavapipe-hlsl 49 float cultmath_cycles_logarithm(float, float) ulp-bounded, platform log 146 4.94622778071426 0 5b2bf1411512c7e5a3f4b335a16fc3ba00ba0784bb87acb14b7d1b9dd551b2f3
lavapipe-hlsl 50 float cultmath_cycles_sqrt(float) ulp-bounded 256 0 0 6d4beb34e93ae304b3d9d30f203fb6c2ba222affc0efc35bb5441528e9062a9a lavapipe-hlsl 50 float cultmath_cycles_sqrt(float) ulp-bounded 256 0 0 6d4beb34e93ae304b3d9d30f203fb6c2ba222affc0efc35bb5441528e9062a9a
lavapipe-hlsl 51 float cultmath_cycles_inverse_sqrt(float) ulp-bounded 256 0 0 71aadd5bd384b9ca2eb89827de53af810a1350b02177de1987e9405e4a6e9ad8 lavapipe-hlsl 51 float cultmath_cycles_inverse_sqrt(float) ulp-bounded 256 0 0 71aadd5bd384b9ca2eb89827de53af810a1350b02177de1987e9405e4a6e9ad8
lavapipe-hlsl 52 float cultmath_cycles_modulo(float, float) ulp-bounded 256 0 0 cf055cafcb5b2d4789a0a2d5cfbc8bc79f52df646b60f5e47db3f994680949e9 lavapipe-hlsl 52 float cultmath_cycles_modulo(float, float) ulp-bounded 256 0 0 cf055cafcb5b2d4789a0a2d5cfbc8bc79f52df646b60f5e47db3f994680949e9
@ -219,16 +221,16 @@ lavapipe-hlsl 91 vec3 cultmath_cycles_mix_val(float, vec3, vec3) ulp-bounded 256
lavapipe-hlsl 92 vec3 cultmath_cycles_mix_color(float, vec3, vec3) ulp-bounded 256 0 0 00067b1567bcd6c07a81fa5002f145dfca22437a3f3df5389b2665334d14909b lavapipe-hlsl 92 vec3 cultmath_cycles_mix_color(float, vec3, vec3) ulp-bounded 256 0 0 00067b1567bcd6c07a81fa5002f145dfca22437a3f3df5389b2665334d14909b
lavapipe-hlsl 93 vec3 cultmath_cycles_mix_soft(float, vec3, vec3) ulp-bounded 157 1.9750388068880254 0 ed7c5508ca0543cc20a5f0c5624e75e3ca2a2d6044d481816eeda2edc50c0592 lavapipe-hlsl 93 vec3 cultmath_cycles_mix_soft(float, vec3, vec3) ulp-bounded 157 1.9750388068880254 0 ed7c5508ca0543cc20a5f0c5624e75e3ca2a2d6044d481816eeda2edc50c0592
lavapipe-hlsl 94 vec3 cultmath_cycles_mix_linear(float, vec3, vec3) ulp-bounded 256 0 0 672435cc30bcbbb253cce7316ee0712b1a18f06924bc89cea2d3388dd164a15d lavapipe-hlsl 94 vec3 cultmath_cycles_mix_linear(float, vec3, vec3) ulp-bounded 256 0 0 672435cc30bcbbb253cce7316ee0712b1a18f06924bc89cea2d3388dd164a15d
lavapipe-hlsl 95 float cultmath_cycles_arcsine(float) ulp-bounded, platform asin 155 2734.286809678319 0 f28f3ed0a53ffeebdfa84865d2d3d41590e6b46fc1578d40e1215e2a9e1af585 lavapipe-hlsl 95 float cultmath_cycles_arcsine(float) ulp-bounded, platform asin 154 2734.286809678319 0 f28f3ed0a53ffeebdfa84865d2d3d41590e6b46fc1578d40e1215e2a9e1af585
lavapipe-hlsl 96 float cultmath_cycles_arccosine(float) ulp-bounded, platform acos 79 1046.8674409774287 0 22a2c38ab5a232cdbb33cf0c133362581a398f64a4d0d5d6dd89dc5af06bd890 lavapipe-hlsl 96 float cultmath_cycles_arccosine(float) ulp-bounded, platform acos 79 1046.8674409774287 0 22a2c38ab5a232cdbb33cf0c133362581a398f64a4d0d5d6dd89dc5af06bd890
lavapipe-hlsl 97 float cultmath_cycles_arctan2(float, float) ulp-bounded, platform atan 7 25.5 0 2c60447d80186f6dc9726f19c09fa96ec3f66d8f5688968da533d9195ea463c4 lavapipe-hlsl 97 float cultmath_cycles_arctan2(float, float) ulp-bounded, platform atan 6 25.5 0 2c60447d80186f6dc9726f19c09fa96ec3f66d8f5688968da533d9195ea463c4
lavapipe-hlsl 98 mat3 cultmath_snoise_hessian(vec3) ulp-bounded 33 2377.8799856935966 0 a64853839f394cc7c367ba5e107caa8bb396f07cf202263aa917095dd1c939f7 lavapipe-hlsl 98 mat3 cultmath_snoise_hessian(vec3) ulp-bounded 33 2377.8799856935966 0 a64853839f394cc7c367ba5e107caa8bb396f07cf202263aa917095dd1c939f7
lavapipe-hlsl 99 mat3 cultmath_snoise_hessian(vec3) ulp-bounded, on the simplex tie set 132 1317.4754224467295 0 9066a49e50731881583ffca16c949171bb61578a1dfd497c54dafe773ab49488 lavapipe-hlsl 99 mat3 cultmath_snoise_hessian(vec3) ulp-bounded, on the simplex tie set 132 1317.4754224467295 0 9066a49e50731881583ffca16c949171bb61578a1dfd497c54dafe773ab49488
lavapipe-hlsl 100 float cultmath_integral_clamp_linear(float, float, float, float, float, float) ulp-bounded 256 0 0 84bf9c9a2126b3114a692507cc54f58d313afc33de8a1cac83969cc508433cbe lavapipe-hlsl 100 float cultmath_integral_clamp_linear(float, float, float, float, float, float) ulp-bounded 256 0 0 84bf9c9a2126b3114a692507cc54f58d313afc33de8a1cac83969cc508433cbe
lavapipe-hlsl 101 float cultmath_integral_clamp_linear(float, float, float, float, float, float) ulp-bounded, lo above hi and infinite bounds 256 0 0 ff999094d7794aa525e0f9f9a067aa21408a59559aa546cace3b1bb4a8c7f96d lavapipe-hlsl 101 float cultmath_integral_clamp_linear(float, float, float, float, float, float) ulp-bounded, lo above hi and infinite bounds 256 0 0 ff999094d7794aa525e0f9f9a067aa21408a59559aa546cace3b1bb4a8c7f96d
lavapipe-hlsl 102 float cultmath_integral_clamp_linear(float, float, float, float, float, float) ulp-bounded, zero-length spans and flat infinite clamps 256 0 0 c3cc48f4edcac6801b9223f12d289934916431bdfaea8f68af33a1b833ab9860 lavapipe-hlsl 102 float cultmath_integral_clamp_linear(float, float, float, float, float, float) ulp-bounded, zero-length spans and flat infinite clamps 256 0 0 c3cc48f4edcac6801b9223f12d289934916431bdfaea8f68af33a1b833ab9860
lavapipe-hlsl 103 SpanNoise cultmath_span_fbm(SpanPath, SpanStage, int, float, float) ulp-bounded 3 1024946.5723514018 0 f6395b9f2609d914894c35b442719be3cc3e11d07c5f0bae5a8719651e60cd4e lavapipe-hlsl 103 SpanNoise cultmath_span_fbm(SpanPath, SpanStage, int, float, float) ulp-bounded 3 1024946.5723514018 0 f6395b9f2609d914894c35b442719be3cc3e11d07c5f0bae5a8719651e60cd4e
lavapipe-hlsl 104 SpanNoise cultmath_span_ridged(SpanPath, SpanStage, int, float, float) ulp-bounded 7 948534.9108393334 0 bfda3536007373bdbef75a5c7bb63a7192f1c90b082984588f23e5b4eb767867 lavapipe-hlsl 104 SpanNoise cultmath_span_ridged(SpanPath, SpanStage, int, float, float) ulp-bounded 7 948534.9108393334 0 2adfdc4e6c81ee0bc7bb7869c55207f5033b4176104bc36dce1528ae0a5fe39b
lavapipe-hlsl 105 vec4 cultmath_snoise_grad(vec3) ulp-bounded, coordinate bands 1e-4 to 1e2 119 423.1430718408491 0 a94c38d1baa651a6faa510920cbe9d1f9528c0adb863ba6e35d835588ad00a58 lavapipe-hlsl 105 vec4 cultmath_snoise_grad(vec3) ulp-bounded, coordinate bands 1e-4 to 1e2 119 423.1430718408491 0 a94c38d1baa651a6faa510920cbe9d1f9528c0adb863ba6e35d835588ad00a58
lavapipe-hlsl 106 vec4 cultmath_snoise_grad(vec3) ulp-bounded, coordinate bands 1e2 to 4e6 73 13116827.1875 0 9ad0fd577b26aa717534c0693df97ee41bcded0768f442739b98f505e4a2a430 lavapipe-hlsl 106 vec4 cultmath_snoise_grad(vec3) ulp-bounded, coordinate bands 1e2 to 4e6 73 13116827.1875 0 9ad0fd577b26aa717534c0693df97ee41bcded0768f442739b98f505e4a2a430
lavapipe-hlsl 107 mat3 cultmath_snoise_hessian(vec3) ulp-bounded, coordinate bands 1e-4 to 1e2 122 980.7417469278579 0 0977fa6f446e4f916a9b938ce24ea32df6aa912dc991c182014d309dc0efb1cb lavapipe-hlsl 107 mat3 cultmath_snoise_hessian(vec3) ulp-bounded, coordinate bands 1e-4 to 1e2 122 980.7417469278579 0 0977fa6f446e4f916a9b938ce24ea32df6aa912dc991c182014d309dc0efb1cb
@ -240,10 +242,12 @@ lavapipe-hlsl 112 vec3 cultmath_segment_closest(vec3, vec3, vec3) ulp-bounded 21
lavapipe-hlsl 113 vec4 cultmath_power_border_step(vec3, vec4, vec4, vec4) ulp-bounded 214 43 0 fdfdff73434a146489b002af5e80663658b2dd7b86105a7e65bdab755ab54a5e lavapipe-hlsl 113 vec4 cultmath_power_border_step(vec3, vec4, vec4, vec4) ulp-bounded 214 43 0 fdfdff73434a146489b002af5e80663658b2dd7b86105a7e65bdab755ab54a5e
lavapipe-hlsl 114 vec4 cultmath_power_score(vec3, vec4) ulp-bounded 207 1.4619722193127032 0 dc1aa639ae1dc44003a0f5d8c7c99b2e3d7f51b158c96b3c3bd7a9f7df6d927e lavapipe-hlsl 114 vec4 cultmath_power_score(vec3, vec4) ulp-bounded 207 1.4619722193127032 0 dc1aa639ae1dc44003a0f5d8c7c99b2e3d7f51b158c96b3c3bd7a9f7df6d927e
lavapipe-hlsl 115 vec4 cultmath_multiplicative_score(vec3, vec4) ulp-bounded 234 1.5529504197086745 0 ee1af26ac82489e8d363e7a6b6b59da7dacfb397d4bc9a723dd68607a69da41a lavapipe-hlsl 115 vec4 cultmath_multiplicative_score(vec3, vec4) ulp-bounded 234 1.5529504197086745 0 ee1af26ac82489e8d363e7a6b6b59da7dacfb397d4bc9a723dd68607a69da41a
lavapipe-hlsl 116 CultSiteCells cultmath_site_cells_insert(CultSiteCells, vec4, int) ulp-bounded 256 0 0 b8687b139aaa865e9f28fbc84fe37dd4a1d4873d5ff8e2074922c07f56551576 lavapipe-hlsl 116 CultSiteCells cultmath_site_cells_insert(CultSiteCells, vec4, int) ulp-bounded 256 0 0 bfad8b369011cfbf14644fec68ce65620694fcbf3cd3804f54953d55587313fc
lavapipe-hlsl 117 vec4 cultmath_site_cells_border(CultSiteCells) ulp-bounded 228 1.069496760346883 0 d7b478bef7e6f80902d0b5b255cb4fca30757ff6b9d25dac2030b037a8d342ce lavapipe-hlsl 117 vec4 cultmath_site_cells_border(CultSiteCells) ulp-bounded 228 1.069496760346883 0 d7b478bef7e6f80902d0b5b255cb4fca30757ff6b9d25dac2030b037a8d342ce
lavapipe-hlsl 118 float cultmath_gabriel_margin(vec3, vec3, vec3) ulp-bounded 201 2 0 6a80fbef486de496a08b7c8e7f9f65e2cc0f5b9f93abe50c43e84ad88d1ed034 lavapipe-hlsl 118 float cultmath_gabriel_margin(vec3, vec3, vec3) ulp-bounded 201 2 0 6a80fbef486de496a08b7c8e7f9f65e2cc0f5b9f93abe50c43e84ad88d1ed034
lavapipe-hlsl 119 vec3 cultmath_lattice_site(vec2, float, float) ulp-bounded 256 0 0 84df739c336796638dcf0a99c328a4b8c150c25d80e02248d4b6d023dc13161f lavapipe-hlsl 119 vec3 cultmath_lattice_site(vec2, float, float) ulp-bounded 256 0 0 84df739c336796638dcf0a99c328a4b8c150c25d80e02248d4b6d023dc13161f
lavapipe-hlsl 120 float cultmath_lattice_weight(float, float, float, float) ulp-bounded 231 2.0671110200639147 0 434cdf5603abc2942aa624b9d2e98d5f1231706c86180a80439bc8b72bdd066e lavapipe-hlsl 120 float cultmath_lattice_weight(float, float, float, float) ulp-bounded 231 2.0671110200639147 0 434cdf5603abc2942aa624b9d2e98d5f1231706c86180a80439bc8b72bdd066e
lavapipe-hlsl 121 float cultmath_lattice_distance(vec2, vec2, float, vec2, float) ulp-bounded 256 0 0 622d0e1ea0fcf0e2700558dbdaea92b8ce02e44048dbf35944c23b5088c3e40c lavapipe-hlsl 121 float cultmath_lattice_distance(vec2, vec2, float, vec2, float) ulp-bounded 256 0 0 ba3fa3bdb4f5f5b2478a4ddfb2e08f2f425e997778a716884e43b13d6ed050b5
lavapipe-hlsl 122 float cultmath_lattice_smooth_step(float, float, float, float) ulp-bounded 256 0 0 e6c9aeb52c5e63ebf3c8525dd39b5b88d5f890fd4f23f4894a92232807a3d94e lavapipe-hlsl 122 float cultmath_lattice_smooth_step(float, float, float, float) ulp-bounded 256 0 0 6cbeddde536b12e83873f2e5ac60d16bef22049d2a7dea61af0ff1e0d5b84740
lavapipe-hlsl 123 vec4 cultmath_power_border(vec3, vec4[128], int) ulp-bounded 188 65.72121683891783 0 8427c2b99109a4f59ff745fdb87f261deb1cbae34ce99bc1ae05ebb7f9963b4d
lavapipe-hlsl 124 vec4 cultmath_weighted_lattice_cells(vec2, float, vec4[128], int, float, float, float, float, float, vec2, float) ulp-bounded 179 3 0 fd3b58c4e2ba8a7bf801a1e6cdff82468abcb368442bc98b2583fda9d2e6f823

View file

@ -16,6 +16,7 @@ import sys
HLSL_VEC = re.compile(r'(float|int|uint)([234])$') HLSL_VEC = re.compile(r'(float|int|uint)([234])$')
GLSL_VEC = re.compile(r'(i|u|)vec([234])$') GLSL_VEC = re.compile(r'(i|u|)vec([234])$')
KIND = {'float': 'f', 'int': 'i', 'uint': 'u'} KIND = {'float': 'f', 'int': 'i', 'uint': 'u'}
ARRAY = re.compile(r'(\w+)\[(\d+)\]$') # an array leaf, vec4[128]: N elements of one leaf type, N x its words
def structs(root): def structs(root):
@ -79,6 +80,19 @@ def body(sig, n, win, wout, defs, glsl):
ret, name, params = m.group(1), m.group(2), [p.strip() for p in m.group(3).split(',') if p.strip()] ret, name, params = m.group(1), m.group(2), [p.strip() for p in m.group(3).split(',') if p.strip()]
lines, offset, args = [], 0, [] lines, offset, args = [], 0, []
for j, p in enumerate(params): for j, p in enumerate(params):
array = ARRAY.match(p)
if array:
elem, size = array.group(1), int(array.group(2))
words = leaves(elem, defs)
lines.append(' %s a%d[%d];' % (elem if glsl else hlsl_type(elem), j, size))
lines.append(' for (uint e = 0u; e < %du; e++) {' % size)
for k, leaf in enumerate(words):
word = ('In.a[b + %du + e * %du]' if glsl else 'In[b + %du + e * %du]') % (offset + k, len(words))
lines.append(' a%d[e]%s = %s;' % (j, access(leaf, glsl), read(leaf[1], glsl, word)))
lines.append(' }')
offset += size * len(words)
args.append('a%d' % j)
continue
ptype = p if glsl else hlsl_type(p) ptype = p if glsl else hlsl_type(p)
lines.append(' %s a%d;' % (ptype, j)) lines.append(' %s a%d;' % (ptype, j))
for leaf in leaves(p, defs): for leaf in leaves(p, defs):

View file

@ -600,14 +600,12 @@ CultCellular cultmath_cellular(float3 p)
return result; return result;
} }
// Sites: a site is float4(position.xyz, weight). A span is a loop over a per-site step, because a shader // Sites: a site is float4(position.xyz, weight). A shader function cannot take a span (design.md, "Sites"), so a span is a
// function cannot take a span in the mirror (design.md, "Sites"). The consumer's loop, over its own buffer: // fixed-capacity array and a count (cultmath_power_border, below), or a loop over a per-site step:
// CultSiteCells cells = cultmath_site_cells_empty(); // CultSiteCells cells = cultmath_site_cells_empty();
// for (int i = 0; i < count; i++) // for (int i = 0; i < count; i++)
// cells = cultmath_site_cells_insert(cells, cultmath_power_score(p, sites[i]), i); // cells = cultmath_site_cells_insert(cells, cultmath_power_score(p, sites[i]), i);
// The exact power cell border is a second loop over the buffer, skipping the owner cells.nearestSite: // The exact power cell border is cultmath_power_border, the second loop over the sites that skips the owner cells.nearestSite.
// float4 border = float4(0.0, 0.0, 0.0, 1.0e30); int owner = (int)cells.nearestSite;
// for (int k = 0; k < count; k++) if (k != owner) border = cultmath_power_border_step(p, sites[owner], sites[k], border);
// The step trusts the owner: a p outside the owner's cell reports distance 0 with that plane's normal, not an error. // The step trusts the owner: a p outside the owner's cell reports distance 0 with that plane's normal, not an error.
// Statement for statement with math.Sites.cs (power_score, multiplicative_score, site_cells_*, gabriel_margin). // Statement for statement with math.Sites.cs (power_score, multiplicative_score, site_cells_*, gabriel_margin).
struct CultSiteCells struct CultSiteCells
@ -688,21 +686,8 @@ float cultmath_gabriel_margin(float3 a, float3 b, float3 other)
return (cultmath_distance(other, (a + b) * 0.5) - r) / r; return (cultmath_distance(other, (a + b) * 0.5) - r) / r;
} }
// Weighted lattice cells: the four per-cell steps of math.Lattice.cs, statement for statement. A shader function cannot take a // Weighted lattice cells: the four per-cell steps of math.Lattice.cs, statement for statement; cultmath_weighted_lattice_cells,
// span (design.md, "Sites"), so weighted_lattice_cells is the consumer's loop over its own body buffer, in two passes: // after them, is the whole function.
// float2 q = p / cellSize; int2 c0 = (int2)floor(q); float f1 = 1.0e30, f2 = 1.0e30; int reach = 0;
// for (int k = 0; ; k++) { // ring pass: Chebyshev rings 0, 1, 2, ... around c0, exact exit
// if (k > 1 && 0.5 * (k - 1) >= max(f2, f1 + 2.0 * smoothing)) break; // k never reaches 38 in the accepted range
// for each cell on ring k: d = cultmath_lattice_distance(q, cultmath_lattice_site(cell, time, drift).xy, w, normal, stretch),
// w = cultmath_lattice_weight(site.z, border, boost, falloff), border = the .w of cultmath_power_border_step
// folded over the bodies (owner = the nearest body of the cell centre (cell + 0.5) * cellSize, z = 0);
// if (d < f1) { f2 = f1; f1 = d; } else if (d < f2) f2 = d; store d for the box pass
// reach = k; }
// float m = f1 + smoothing; // box pass: every stored cell, rows dz = -reach..reach, then dx = -reach..reach
// for each (dz, dx): m = cultmath_lattice_smooth_step(m, d, f1, smoothing); // strict < for the winning id (earlier cell wins ties)
// The preconditions the C# function throws for are the caller's in a shader: finite arguments, cellSize > 0, boost in [0, 4],
// falloff > 0, smoothing in (0, 1], stretch in [1, 2], p within 2^23 cells. The normal is normalised as C# does: divide by its
// largest component, then by its length (a zero normal means (1, 0) and needs stretch == 1). The ring bound is 37.
float3 cultmath_lattice_site(float2 cell, float time, float drift) float3 cultmath_lattice_site(float2 cell, float time, float drift)
{ {
int3 h = cultmath_pcg3d(int3((int)cell.x, (int)cell.y, 0)); int3 h = cultmath_pcg3d(int3((int)cell.x, (int)cell.y, 0));
@ -734,6 +719,90 @@ float cultmath_lattice_smooth_step(float m, float d, float f1, float smoothing)
return min(next, f1 + smoothing); return min(next, f1 + smoothing);
} }
// Sites by value: a shader function cannot take a span, so these take float4 sites[CULTMATH_MAX_SITES] and the count in use, with
// count in [0, CULTMATH_MAX_SITES]. A consumer defines CULTMATH_MAX_SITES before including this file to change the capacity; loops
// run to count, so the capacity costs uniform space, not iterations. A caller whose sites live in a buffer copies them into a local array first.
// The preconditions the C# functions throw for are the caller's in a shader: finite arguments, cellSize > 0, boost in [0, 4],
// falloff > 0, smoothing in (0, 1], stretch in [1, 2], p within 2^23 cells. A zero stretchNormal means (1, 0) and needs stretch == 1.
// cultmath_weighted_lattice_cells is math.weighted_lattice_cells: the ring pass is bounded by k <= 37 and exits as C# does, and the
// box pass recomputes each distance instead of caching it (a 75 x 75 private array is not a shader option), with the same values.
#ifndef CULTMATH_MAX_SITES
#define CULTMATH_MAX_SITES 128
#endif
float4 cultmath_power_border(float3 p, float4 sites[CULTMATH_MAX_SITES], int count)
{
CultSiteCells cells = cultmath_site_cells_empty();
for (int i = 0; i < count; i++)
cells = cultmath_site_cells_insert(cells, cultmath_power_score(p, sites[i]), i);
float4 border = float4(0.0, 0.0, 0.0, 1.0e30);
int owner = (int)cells.nearestSite;
if (owner < 0)
return border;
for (int k = 0; k < count; k++)
if (k != owner)
border = cultmath_power_border_step(p, sites[owner], sites[k], border);
return border;
}
float cultmath_lattice_cell_distance(float2 q, float2 cell, float time, float drift, float4 bodies[CULTMATH_MAX_SITES], int count,
float cellSize, float boost, float falloff, float2 normal, float stretch)
{
float3 site = cultmath_lattice_site(cell, time, drift);
float border = cultmath_power_border(float3((cell + 0.5) * cellSize, 0.0), bodies, count).w;
return cultmath_lattice_distance(q, site.xy, cultmath_lattice_weight(site.z, border, boost, falloff), normal, stretch);
}
float4 cultmath_weighted_lattice_cells(float2 p, float time, float4 bodies[CULTMATH_MAX_SITES], int count, float cellSize, float drift,
float boost, float falloff, float smoothing, float2 stretchNormal, float stretch)
{
float scale = max(abs(stretchNormal.x), abs(stretchNormal.y));
float2 normal = float2(1.0, 0.0);
if (scale > 0.0)
{
normal = stretchNormal / scale;
normal = normal / length(normal);
}
float2 q = p / cellSize;
float2 origin = floor(q);
int cx0 = (int)origin.x;
int cz0 = (int)origin.y;
float f1 = 1.0e30;
float f2 = 1.0e30;
int reach = 0;
for (int k = 0; k <= 37; k++)
{
if (k > 1 && 0.5 * (float)(k - 1) >= max(f2, f1 + 2.0 * smoothing))
break;
for (int dz = -k; dz <= k; dz++)
{
int step = dz == -k || dz == k || k == 0 ? 1 : 2 * k;
for (int dx = -k; dx <= k; dx += step)
{
float d = cultmath_lattice_cell_distance(q, float2(cx0 + dx, cz0 + dz), time, drift, bodies, count, cellSize, boost, falloff, normal, stretch);
if (d < f1) { f2 = f1; f1 = d; }
else if (d < f2) f2 = d;
}
}
reach = k;
}
float m = f1 + smoothing;
float best = 1.0e30;
float second = 1.0e30;
int winDx = 0;
int winDz = 0;
for (int dz = -reach; dz <= reach; dz++)
for (int dx = -reach; dx <= reach; dx++)
{
float d = cultmath_lattice_cell_distance(q, float2(cx0 + dx, cz0 + dz), time, drift, bodies, count, cellSize, boost, falloff, normal, stretch);
if (d < best) { second = best; best = d; winDx = dx; winDz = dz; }
else if (d < second) second = d;
m = cultmath_lattice_smooth_step(m, d, f1, smoothing);
}
int4 id = cultmath_pcg4d(int4(cx0 + winDx, cz0 + winDz, 0, 0));
return float4(best, second, m, (float)((uint)id.w >> 8) * (1.0 / 16777216.0));
}
float2 cultmath_segment_closest(float2 point_value, float2 start, float2 end) float2 cultmath_segment_closest(float2 point_value, float2 start, float2 end)
{ {
float2 segment = end - start; float2 segment = end - start;