From 355cfa2a3da1095790b117c8fefd72dbe783e12e Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 12 Oct 2026 04:07:08 +0200 Subject: [PATCH] CultMath: cycles_ramp_index casts its int operands explicitly (GLSL ES 3.00 has no implicit int to float); README and GLSL file-contract tests name the Cycles files Builds. The oracle fixtures and CultMath.Cycles.glsl are still to come, so the oracle and golden tests fail here. Co-Authored-By: Claude Sonnet 5.5 --- packages/cultmath/README.md | 22 +++++++++++++++---- .../cultmath/shaders/CultMath.Cycles.hlsl | 5 +++-- packages/cultmath/src/CultMath/math.Cycles.cs | 5 +++-- .../tests/CultMath.Tests/GlslMirrorTests.cs | 13 +++++++++++ .../Shaders/CultMath.Cycles.hlsl | 5 +++-- 5 files changed, 40 insertions(+), 10 deletions(-) diff --git a/packages/cultmath/README.md b/packages/cultmath/README.md index e0dd7179..24662be7 100644 --- a/packages/cultmath/README.md +++ b/packages/cultmath/README.md @@ -33,9 +33,20 @@ shader semantics. - `shaders/CultMath.hlsl`, a canonical HLSL mirror include for shader-side parity. HLSL already owns `float2`, `float3`, and `float4`; the include exposes `cultmath_*` functions for shared semantics HLSL does not provide. - It includes `shaders/CultMath.Phacelle.hlsl` (MPL-2.0) and + It includes `shaders/CultMath.Phacelle.hlsl` (MPL-2.0), + `shaders/CultMath.Cycles.hlsl` (Apache-2.0) and `shaders/CultMath.Interval.hlsl` and `shaders/CultMath.Affine.hlsl` from the same - directory, so ship all four. + directory, so ship all five. +- Cycles node semantics (Apache-2.0): `cycles_*` functions that compute what + Blender's Cycles renderer computes for the Math node operations whose results + differ from a plain intrinsic (`cycles_divide`, `cycles_power`, `cycles_modulo`, + `cycles_wrap`, `cycles_pingpong`, `cycles_smooth_min`, ...), Map Range + (`cycles_map_range_linear`, `_stepped`, `_smoothstep`, `_smootherstep`), Clamp + (`cycles_clamp_minmax`, `cycles_clamp_range`), Mix (`cycles_mix_float`, + `cycles_mix_vector` and every colour blend mode as `cycles_mix_`), RGB to BW, + and `cycles_ramp_index`, where a Color Ramp reads its table. `cultmath_cycles_*` in + HLSL and GLSL. Operands are finite floats, and the tests hold every function to + the results of Blender 5.2.2's own CPU Cycles (`tools/cycles-oracle`). - interval arithmetic for culling empty space: `iv_*` functions over `float2(lo, hi)` (`iv_add`, `iv_mul`, `iv_exp`, `iv_smoothstep`, ...) and `iv_snoise_ball`/`iv_fbm_ball`, which bound `snoise` and `fbm_grad` over a @@ -58,11 +69,14 @@ shader semantics. - `shaders/CultMath.glsl` (MIT), the same `cultmath_*` library in GLSL ES 3.00 for WebGL2, generated from `CultMath.hlsl` (never edited by hand), and `shaders/CultMath.Phacelle.glsl` (MPL-2.0), generated from - `CultMath.Phacelle.hlsl`, which holds `cultmath_phacelle` alone. GLSL has no + `CultMath.Phacelle.hlsl`, which holds `cultmath_phacelle` alone, and + `shaders/CultMath.Cycles.glsl` (Apache-2.0), generated from + `CultMath.Cycles.hlsl`, which holds the `cultmath_cycles_*` functions. GLSL has no `#include`: a WebGL consumer concatenates `CultMath.glsl` after its own `#version 300 es` line and `precision highp float; precision highp int;` (the library declares no precision, and its hashes need 32-bit integers), - then `CultMath.Phacelle.glsl` only if it calls `cultmath_phacelle`, then its + then `CultMath.Phacelle.glsl` only if it calls `cultmath_phacelle` and + `CultMath.Cycles.glsl` only if it calls a `cultmath_cycles_` function, then its own shader source. - `Voronoi.SampleTones`, a C# batch surface that calls the Rust `cultmath-core` native kernel when `cultmath_core` is available and falls back diff --git a/packages/cultmath/shaders/CultMath.Cycles.hlsl b/packages/cultmath/shaders/CultMath.Cycles.hlsl index 60aa0ce0..5102b6dc 100644 --- a/packages/cultmath/shaders/CultMath.Cycles.hlsl +++ b/packages/cultmath/shaders/CultMath.Cycles.hlsl @@ -335,9 +335,10 @@ float cultmath_cycles_rgb_to_bw(float3 color) // otherwise. The ramp table itself is the caller's. float2 cultmath_cycles_ramp_index(float t, int size) { - float f = (t > 0.0 ? (t < 1.0 ? t : 1.0) : 0.0) * (size - 1); + float f = (t > 0.0 ? (t < 1.0 ? t : 1.0) : 0.0) * (float)(size - 1); int index = clamp((int)f, 0, size - 1); - return float2(index, f - index); + float lower = (float)index; + return float2(lower, f - lower); } #endif diff --git a/packages/cultmath/src/CultMath/math.Cycles.cs b/packages/cultmath/src/CultMath/math.Cycles.cs index 2bb9185a..01bbe7dd 100644 --- a/packages/cultmath/src/CultMath/math.Cycles.cs +++ b/packages/cultmath/src/CultMath/math.Cycles.cs @@ -361,8 +361,9 @@ public static partial class math /// public static float2 cycles_ramp_index(float t, int size) { - var f = (t > 0.0f ? (t < 1.0f ? t : 1.0f) : 0.0f) * (size - 1); + var f = (t > 0.0f ? (t < 1.0f ? t : 1.0f) : 0.0f) * (float)(size - 1); var index = clamp((int)f, 0, size - 1); - return new float2(index, f - index); + var lower = (float)index; + return new float2(lower, f - lower); } } diff --git a/packages/cultmath/tests/CultMath.Tests/GlslMirrorTests.cs b/packages/cultmath/tests/CultMath.Tests/GlslMirrorTests.cs index 4c6243cf..adcff740 100644 --- a/packages/cultmath/tests/CultMath.Tests/GlslMirrorTests.cs +++ b/packages/cultmath/tests/CultMath.Tests/GlslMirrorTests.cs @@ -86,6 +86,12 @@ public sealed class GlslMirrorTests Assert.DoesNotContain(token, library); Assert.Contains(token, phacelle); } + + // The Apache-2.0 code is in its own file too, and the MPL-2.0 file holds none of it. + var cycles = lowered.Single(output => output.File == "CultMath.Cycles.glsl").Text; + Assert.DoesNotContain("cultmath_cycles_divide(", library); + Assert.DoesNotContain("cultmath_cycles_divide(", phacelle); + Assert.Contains("float cultmath_cycles_divide(", cycles); } // The two-file contract a consumer relies on when it concatenates CultMath.Phacelle.glsl after @@ -106,6 +112,13 @@ public sealed class GlslMirrorTests Assert.Contains("cultmath_phacelle is in CultMath.Phacelle.glsl", library); Assert.Contains(" MPL-2.0;", phacelle); Assert.DoesNotContain("MIT", phacelle); + + var cycles = lowered.Single(output => output.File == "CultMath.Cycles.glsl").Text.Split(' +')[0]; + Assert.Contains("cultmath_cycles_ramp_index is in CultMath.Cycles.glsl", library); + Assert.Contains(" Apache-2.0;", cycles); + Assert.DoesNotContain("MIT", cycles); + Assert.DoesNotContain("MPL", cycles); } [Fact] diff --git a/packages/cultmath/unity/org.gamecult.cultmath/Shaders/CultMath.Cycles.hlsl b/packages/cultmath/unity/org.gamecult.cultmath/Shaders/CultMath.Cycles.hlsl index 60aa0ce0..5102b6dc 100644 --- a/packages/cultmath/unity/org.gamecult.cultmath/Shaders/CultMath.Cycles.hlsl +++ b/packages/cultmath/unity/org.gamecult.cultmath/Shaders/CultMath.Cycles.hlsl @@ -335,9 +335,10 @@ float cultmath_cycles_rgb_to_bw(float3 color) // otherwise. The ramp table itself is the caller's. float2 cultmath_cycles_ramp_index(float t, int size) { - float f = (t > 0.0 ? (t < 1.0 ? t : 1.0) : 0.0) * (size - 1); + float f = (t > 0.0 ? (t < 1.0 ? t : 1.0) : 0.0) * (float)(size - 1); int index = clamp((int)f, 0, size - 1); - return float2(index, f - index); + float lower = (float)index; + return float2(lower, f - lower); } #endif