CultLib/packages/cultmath/shaders/CultMath.Affine.hlsl
Meta Vens 58a2d50a76 cultmath: one owner of the snoise read's absolute terms (SNOISE_READ_VALUE, SNOISE_READ_GRADIENT)
span_slack and af_snoise each spelled 2^-14 (value) and 2^-12 (gradient) inline, in C# and
HLSL. Both now read the constants beside SNOISE_HESSIAN (math.Affine.cs, CultMath.Affine.hlsl,
Unity copies), so a device-judged widening moves every reader. Bits unchanged. The test
oracles in AffineTests and GlslMirrorTests read the constants; a new test pins af_snoise's
allowance to span_slack's terms. span_slack's comment no longer claims it is the only
allowance. Does not build green yet: CultMath.glsl is not regenerated in this commit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 06:34:21 +02:00

142 lines
5.9 KiB
HLSL

#ifndef CULTMATH_AFFINE_HLSL
#define CULTMATH_AFFINE_HLSL
// Reduced affine forms with one shared symbol over float3(x0, a, e): the set { x0 + a eps + e delta :
// delta in [-1, 1] } at the region's shared eps in [-1, 1], e >= 0. Every cultmath_af_* function mirrors
// math.Affine.cs bit for bit; see that file and docs/design.md ("Affine forms") for each op's enclosure
// and the float32 rounding allowance it folds into e. Included by CultMath.hlsl after
// CultMath.Interval.hlsl, whose CULTMATH_SNOISE_LIPSCHITZ it shares, and after its own
// definition of cultmath_snoise_grad.
// An upper bound on the spectral norm of the Hessian of cultmath_snoise(float3); provenance in
// math.Affine.cs (SNOISE_HESSIAN).
static const float CULTMATH_SNOISE_HESSIAN = 55.642143;
// The absolute rounding of one float32 read of cultmath_snoise_grad, value and gradient; the owner is
// math.Affine.cs (SNOISE_READ_VALUE, SNOISE_READ_GRADIENT), read here by cultmath_af_snoise and in
// CultMath.hlsl by cultmath_span_slack.
static const float CULTMATH_SNOISE_READ_VALUE = 6.103515625e-5;
static const float CULTMATH_SNOISE_READ_GRADIENT = 2.44140625e-4;
// An upper bound on the operator norm of the third-derivative tensor of cultmath_snoise(float3), the
// next order after CULTMATH_SNOISE_HESSIAN; provenance in math.Span.cs (SNOISE_THIRD).
static const float CULTMATH_SNOISE_THIRD = 535.8009;
float3 cultmath_af_point(float value) { return float3(value, 0.0, 0.0); }
float3 cultmath_af_symbol(float x0, float a) { return float3(x0, a, 0.0); }
float3 cultmath_af_from_iv(float2 interval)
{
float x0 = interval.x * 0.5 + interval.y * 0.5;
float e = interval.y * 0.5 - interval.x * 0.5;
return float3(x0, 0.0, e + (abs(x0) + e) * 9.5367431640625e-7 + 1.17549435e-38);
}
// An endpoint that would be NaN is the infinity on its side. NaN is found by its bit pattern: dxc marks
// compares fast, so a test that needs NaN to make `lo == lo` false is deleted under its default flags.
float2 cultmath_af_range(float3 x)
{
float r = abs(x.y) + x.z;
float w = abs(x.x) * 9.5367431640625e-7 + r * 9.5367431640625e-7;
float lo = x.x - r - w;
float hi = x.x + r + w;
return float2((asuint(lo) & 0x7fffffffu) > 0x7f800000u ? asfloat(0xff800000u) : lo,
(asuint(hi) & 0x7fffffffu) > 0x7f800000u ? asfloat(0x7f800000u) : hi);
}
float3 cultmath_af_add(float3 x, float3 y)
{
float x0 = x.x + y.x;
float a = x.y + y.y;
float e = x.z + y.z;
return float3(x0, a, e + (abs(x0) + abs(a) + e) * 9.5367431640625e-7);
}
float3 cultmath_af_sub(float3 x, float3 y)
{
float x0 = x.x - y.x;
float a = x.y - y.y;
float e = x.z + y.z;
return float3(x0, a, e + (abs(x0) + abs(a) + e) * 9.5367431640625e-7);
}
float3 cultmath_af_neg(float3 x) { return float3(-x.x, -x.y, x.z); }
float3 cultmath_af_scale(float3 x, float s)
{
float x0 = x.x * s;
float a = x.y * s;
float e = x.z * abs(s);
return float3(x0, a, e + (abs(x0) + abs(a) + e) * 9.5367431640625e-7 + 1.17549435e-38);
}
float3 cultmath_af_add_iv(float3 x, float2 interval) { return cultmath_af_add(x, cultmath_af_from_iv(interval)); }
float3 cultmath_af_mul(float3 x, float3 y)
{
float x0 = x.x * y.x;
float p = x.x * y.y;
float q = y.x * x.y;
float a = p + q;
float e = abs(x.x) * y.z + abs(y.x) * x.z + (abs(x.y) + x.z) * (abs(y.y) + y.z);
return float3(x0, a, e + (abs(x0) + abs(p) + abs(q) + e) * 9.5367431640625e-7 + 1.17549435e-38);
}
// The narrower of the Lipschitz form and the centred (Taylor) form over every p = centre + axis eps + w,
// |w| <= radius, from one cultmath_snoise_grad at the centre; the allowance for the float32 evaluation is
// in math.Affine.cs.
float3 cultmath_af_snoise(float3 centre, float3 axis, float radius)
{
float4 g = cultmath_snoise_grad(centre);
float3 gradient = float3(g.x, g.y, g.z);
float reach = length(axis) + radius;
float lipschitz = CULTMATH_SNOISE_LIPSCHITZ * reach;
float a = dot(gradient, axis);
float remainder = length(gradient) * radius + 0.5 * CULTMATH_SNOISE_HESSIAN * reach * reach;
float allowance = CULTMATH_SNOISE_READ_VALUE + CULTMATH_SNOISE_READ_GRADIENT * reach
+ CULTMATH_SNOISE_LIPSCHITZ * 9.5367431640625e-7 * (abs(centre.x) + abs(centre.y) + abs(centre.z) + reach);
bool centred = abs(a) + remainder < lipschitz;
float slope = centred ? a : 0.0;
float e = (centred ? remainder : lipschitz) + allowance;
return float3(g.w, slope, e + (abs(g.w) + abs(slope) + e) * 9.5367431640625e-7);
}
// Octave sum of cultmath_af_snoise, compounding frequency and amplitude as the value-and-gradient fBm does.
float3 cultmath_af_fbm(float3 centre, float3 axis, float radius, int octaves, float lacunarity, float gain)
{
octaves = clamp(octaves, 0, 16);
float amplitude = 1.0;
float frequency = 1.0;
float3 sum = float3(0.0, 0.0, 0.0);
for (int i = 0; i < octaves; i++)
{
sum = cultmath_af_add(sum, cultmath_af_scale(cultmath_af_snoise(centre * frequency, axis * frequency, radius * abs(frequency)), amplitude));
frequency *= lacunarity;
amplitude *= gain;
}
return sum;
}
// The axis of a screen tile's slice over the depth segment [z0, z1]; camera frame of
// cultmath_iv_frustum_ball.
float3 cultmath_af_frustum_axis(float2 centreSlope, float z0, float z1)
{
float h = (z1 - z0) * 0.5;
return float3(centreSlope.x * h, centreSlope.y * h, h);
}
// The affine slice of a screen tile over [z0, z1]: centre xyz, radius w. Derivation in math.Affine.cs.
float4 cultmath_af_frustum_ball(float2 centreSlope, float z0, float z1, float footprintPerDepth, float warpVariation)
{
float zm = (z0 + z1) * 0.5;
float h = (z1 - z0) * 0.5;
float cx = centreSlope.x * zm;
float cy = centreSlope.y * zm;
float spread = (abs(centreSlope.x) + abs(centreSlope.y) + 1.0) * h;
float radius = z1 * footprintPerDepth + warpVariation;
radius += (abs(cx) + abs(cy) + abs(zm) + spread + radius) * 9.5367431640625e-7;
return float4(cx, cy, zm, radius);
}
#endif