infinite.json holds 47 cases of infinite operands for cycles_power, cycles_modulo and Map Range Smooth Step, made inside Blender's kernel; only that family was regenerated. CultMath.Cycles.glsl changed by the generator alone (CULTMATH_WRITE_GLSL=1); glsl-parity.json and CultMath.glsl are byte-identical. This commit builds and its tests are expected green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
363 lines
15 KiB
GLSL
363 lines
15 KiB
GLSL
// Generated from shaders/CultMath.Cycles.hlsl by GlslLowering (packages/cultmath/tests/CultMath.Tests/GlslLowering.cs). Do not edit; regenerate with CULTMATH_WRITE_GLSL=1 dotnet test --filter GlslMirrorTests. Apache-2.0; requires CultMath.glsl before it.
|
|
#ifndef CULTMATH_CYCLES_GLSL
|
|
#define CULTMATH_CYCLES_GLSL
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
// Cycles node semantics: what Blender's Cycles renderer computes for the Math, Map Range, Clamp, Mix and
|
|
// RGB to BW nodes and for a Color Ramp lookup, ported function by function from the Cycles kernel
|
|
// (intern/cycles of the Blender v5.2.2 tag; SPDX-FileCopyrightText: 2011-2022 Blender Foundation, Apache-2.0):
|
|
// kernel/svm/{math_util,map_range,clamp,mix,color_util,ramp,convert}.h, util/math_base.h, util/color.h and
|
|
// util/math_float3.h. Each function's comment names its Cycles source. Modified by GameCult: Cycles C++
|
|
// becomes HLSL, a function per node operation, and the node's socket defaults and clamp options are left to
|
|
// the caller. This file is Apache-2.0 (section 4: it was changed); CultMath.hlsl, which includes it after its
|
|
// own definitions, is MIT (see THIRD-PARTY-NOTICES.md). math.Cycles.cs mirrors it bit for bit.
|
|
//
|
|
// Operands are finite floats. NaN and infinite operands are outside the oracle's reach and the mirror's.
|
|
|
|
// FLT_EPSILON, the least epsilon a Compare node honours.
|
|
const float CULTMATH_CYCLES_FLT_EPSILON = 1.1920929e-07;
|
|
|
|
// The scene-linear luminance coefficients Cycles reads from Blender 5.2.2's default OCIO config
|
|
// (kernel/film linear_rgb_to_gray). The oracle fixture pins them with a case per primary.
|
|
const float CULTMATH_CYCLES_RGB_TO_Y_R = 0.212639093;
|
|
const float CULTMATH_CYCLES_RGB_TO_Y_G = 0.715169132;
|
|
const float CULTMATH_CYCLES_RGB_TO_Y_B = 0.0721927434;
|
|
|
|
// ---- Math node (kernel/svm/math_util.h svm_math; util/math_base.h) ----
|
|
|
|
// safe_divide
|
|
float cultmath_cycles_divide(float a, float b) { return b != 0.0 ? a / b : 0.0; }
|
|
|
|
// safe_powf with compatible_powf. A negative base takes an integral exponent's parity; a base below zero
|
|
// with a fractional exponent is 0.
|
|
float cultmath_cycles_power(float a, float b)
|
|
{
|
|
float whole = b >= 0.0 ? floor(b) : ceil(b);
|
|
if (a < 0.0 && b != whole)
|
|
return 0.0;
|
|
if (b == 0.0)
|
|
return 1.0;
|
|
if (a == 0.0)
|
|
return 0.0;
|
|
if (a < 0.0)
|
|
{
|
|
float magnitude = pow(-a, b);
|
|
// Odd exactly when the remainder is 1: an infinite exponent leaves NaN here and is even, as powf(a, +-inf) is never negative.
|
|
return b - 2.0 * floor(0.5 * b) == 1.0 ? -magnitude : magnitude;
|
|
}
|
|
return pow(a, b);
|
|
}
|
|
|
|
// safe_logf
|
|
float cultmath_cycles_logarithm(float a, float b)
|
|
{
|
|
if (a <= 0.0 || b <= 0.0)
|
|
return 0.0;
|
|
float denominator = log(b);
|
|
return denominator != 0.0 ? log(a) / denominator : 0.0;
|
|
}
|
|
|
|
// safe_sqrtf
|
|
float cultmath_cycles_sqrt(float a) { return sqrt(max(a, 0.0)); }
|
|
|
|
// inversesqrtf
|
|
float cultmath_cycles_inverse_sqrt(float a) { return a > 0.0 ? 1.0 / sqrt(a) : 0.0; }
|
|
|
|
// safe_modulo is fmodf, the remainder with the dividend's sign, exactly. GLSL has no fmod, and a - b trunc(a / b)
|
|
// is wrong where the quotient rounds up to an integer or the product rounds (3 mod 0.3 is 0.29999995, not 0).
|
|
// So this subtracts the largest power-of-two multiple of the divisor not above the remainder until the
|
|
// remainder is below the divisor: every step is an exact subtraction, as in fmod. The loops are bounded by the
|
|
// binades a float spans.
|
|
float cultmath_cycles_modulo(float a, float b)
|
|
{
|
|
float remainder = abs(a);
|
|
float divisor = abs(b);
|
|
for (int steps = 0; steps < 300 && remainder >= divisor && divisor > 0.0; steps++)
|
|
{
|
|
float multiple = divisor;
|
|
for (int doublings = 0; doublings < 300 && multiple * 2.0 <= remainder; doublings++)
|
|
multiple = multiple * 2.0;
|
|
remainder = remainder - multiple;
|
|
}
|
|
return b != 0.0 ? (a < 0.0 ? -remainder : remainder) : 0.0;
|
|
}
|
|
|
|
// safe_floored_modulo; a build that contracts it differs by a bound stated at cycles_floored_modulo in math.Cycles.cs and the README
|
|
float cultmath_cycles_floored_modulo(float a, float b) { return b != 0.0 ? a - floor(a / b) * b : 0.0; }
|
|
|
|
// wrapf (note the argument order: value, max, min); contraction bound as for floored_modulo
|
|
float cultmath_cycles_wrap(float value, float max_value, float min_value)
|
|
{
|
|
float range = max_value - min_value;
|
|
return range != 0.0 ? value - (range * floor((value - min_value) / range)) : min_value;
|
|
}
|
|
|
|
// floorf(safe_divide(a, b)) * b
|
|
float cultmath_cycles_snap(float a, float b) { return floor(cultmath_cycles_divide(a, b)) * b; }
|
|
|
|
// pingpongf
|
|
float cultmath_cycles_pingpong(float a, float b)
|
|
{
|
|
float scaled = (a - b) / (b * 2.0);
|
|
return b != 0.0 ? abs((scaled - floor(scaled)) * b * 2.0 - b) : 0.0;
|
|
}
|
|
|
|
// smoothminf
|
|
float cultmath_cycles_smooth_min(float a, float b, float k)
|
|
{
|
|
float h = max(k - abs(a - b), 0.0) / k;
|
|
return k != 0.0 ? min(a, b) - h * h * h * k * 0.16666667 : min(a, b);
|
|
}
|
|
|
|
// -smoothminf(-a, -b, k)
|
|
float cultmath_cycles_smooth_max(float a, float b, float k) { return -cultmath_cycles_smooth_min(-a, -b, k); }
|
|
|
|
// NODE_MATH_COMPARE
|
|
float cultmath_cycles_compare(float a, float b, float epsilon)
|
|
{
|
|
return (a == b || abs(a - b) <= max(epsilon, CULTMATH_CYCLES_FLT_EPSILON)) ? 1.0 : 0.0;
|
|
}
|
|
|
|
// NODE_MATH_ROUND
|
|
float cultmath_cycles_round(float a) { return floor(a + 0.5); }
|
|
|
|
// NODE_MATH_TRUNC
|
|
float cultmath_cycles_trunc(float a) { return a >= 0.0 ? floor(a) : ceil(a); }
|
|
|
|
// NODE_MATH_FRACTION
|
|
float cultmath_cycles_fract(float a) { return a - floor(a); }
|
|
|
|
// compatible_signf
|
|
float cultmath_cycles_sign(float a) { return a == 0.0 ? 0.0 : (a < 0.0 ? -1.0 : 1.0); }
|
|
|
|
// safe_asinf
|
|
float cultmath_cycles_arcsine(float a) { return asin(clamp(a, -1.0, 1.0)); }
|
|
|
|
// safe_acosf
|
|
float cultmath_cycles_arccosine(float a) { return acos(clamp(a, -1.0, 1.0)); }
|
|
|
|
// compatible_atan2 (a is y, b is x)
|
|
float cultmath_cycles_arctan2(float a, float b) { return (b == 0.0 && a == 0.0) ? 0.0 : atan(a, b); }
|
|
|
|
// ---- Map Range node (kernel/svm/map_range.h svm_node_map_range) ----
|
|
// A Map Range node's Clamp option is a Clamp node of type Range on its result (MapRangeNode::expand):
|
|
// compose cultmath_cycles_clamp_range over these.
|
|
|
|
float cultmath_cycles_map_range_linear(float value, float from_min, float from_max, float to_min, float to_max)
|
|
{
|
|
float factor = (value - from_min) / (from_max - from_min);
|
|
return from_max != from_min ? to_min + factor * (to_max - to_min) : 0.0;
|
|
}
|
|
|
|
float cultmath_cycles_map_range_stepped(float value, float from_min, float from_max, float to_min, float to_max, float steps)
|
|
{
|
|
float factor = (value - from_min) / (from_max - from_min);
|
|
factor = steps > 0.0 ? floor(factor * (steps + 1.0)) / steps : 0.0;
|
|
return from_max != from_min ? to_min + factor * (to_max - to_min) : 0.0;
|
|
}
|
|
|
|
// The value itself, not a normalized one, goes through smoothstep(from_min, from_max, .), flipped when
|
|
// from_min > from_max (util/math_base.h smoothstep).
|
|
float cultmath_cycles_map_range_smoothstep(float value, float from_min, float from_max, float to_min, float to_max)
|
|
{
|
|
float low = min(from_min, from_max);
|
|
float high = max(from_min, from_max);
|
|
float t = (value - low) / (high - low);
|
|
float eased = value < low ? 0.0 : (value >= high ? 1.0 : (3.0 - 2.0 * t) * (t * t));
|
|
float factor = from_min > from_max ? 1.0 - eased : eased;
|
|
return from_max != from_min ? to_min + factor * (to_max - to_min) : 0.0;
|
|
}
|
|
|
|
// smootherstep of map_range.h, flipped when from_min > from_max.
|
|
float cultmath_cycles_map_range_smootherstep(float value, float from_min, float from_max, float to_min, float to_max)
|
|
{
|
|
float low = min(from_min, from_max);
|
|
float high = max(from_min, from_max);
|
|
float x = clamp(cultmath_cycles_divide(value - low, high - low), 0.0, 1.0);
|
|
float eased = x * x * x * (x * (x * 6.0 - 15.0) + 10.0);
|
|
float factor = from_min > from_max ? 1.0 - eased : eased;
|
|
return from_max != from_min ? to_min + factor * (to_max - to_min) : 0.0;
|
|
}
|
|
|
|
// ---- Clamp node (kernel/svm/clamp.h svm_node_clamp) ----
|
|
|
|
// Type Min Max
|
|
float cultmath_cycles_clamp_minmax(float value, float min_value, float max_value) { return clamp(value, min_value, max_value); }
|
|
|
|
// Type Range: the bounds swap when min_value > max_value.
|
|
float cultmath_cycles_clamp_range(float value, float min_value, float max_value)
|
|
{
|
|
return min_value > max_value ? clamp(value, max_value, min_value) : clamp(value, min_value, max_value);
|
|
}
|
|
|
|
// ---- Mix node (kernel/svm/mix.h, color_util.h) ----
|
|
// Factor clamping (svm_node_mix_color's saturatef) and result clamping (saturate) are the caller's.
|
|
|
|
// svm_node_mix_float
|
|
float cultmath_cycles_mix_float(float t, float a, float b) { return a * (1.0 - t) + b * t; }
|
|
|
|
// svm_node_mix_vector
|
|
vec3 cultmath_cycles_mix_vector(float t, vec3 a, vec3 b) { return a * (vec3(1.0, 1.0, 1.0) - t) + b * t; }
|
|
|
|
vec3 cultmath_cycles_mix_blend(float t, vec3 col1, vec3 col2) { return col1 + t * (col2 - col1); }
|
|
|
|
vec3 cultmath_cycles_mix_add(float t, vec3 col1, vec3 col2) { return col1 + t * ((col1 + col2) - col1); }
|
|
|
|
vec3 cultmath_cycles_mix_mul(float t, vec3 col1, vec3 col2) { return col1 + t * (col1 * col2 - col1); }
|
|
|
|
vec3 cultmath_cycles_mix_screen(float t, vec3 col1, vec3 col2)
|
|
{
|
|
float tm = 1.0 - t;
|
|
vec3 one = vec3(1.0, 1.0, 1.0);
|
|
return one - (vec3(tm, tm, tm) + t * (one - col2)) * (one - col1);
|
|
}
|
|
|
|
vec3 cultmath_cycles_mix_overlay(float t, vec3 col1, vec3 col2)
|
|
{
|
|
float tm = 1.0 - t;
|
|
return vec3(
|
|
col1.x < 0.5 ? col1.x * (tm + 2.0 * t * col2.x) : 1.0 - (tm + 2.0 * t * (1.0 - col2.x)) * (1.0 - col1.x),
|
|
col1.y < 0.5 ? col1.y * (tm + 2.0 * t * col2.y) : 1.0 - (tm + 2.0 * t * (1.0 - col2.y)) * (1.0 - col1.y),
|
|
col1.z < 0.5 ? col1.z * (tm + 2.0 * t * col2.z) : 1.0 - (tm + 2.0 * t * (1.0 - col2.z)) * (1.0 - col1.z));
|
|
}
|
|
|
|
vec3 cultmath_cycles_mix_sub(float t, vec3 col1, vec3 col2) { return col1 + t * ((col1 - col2) - col1); }
|
|
|
|
vec3 cultmath_cycles_mix_div(float t, vec3 col1, vec3 col2)
|
|
{
|
|
float tm = 1.0 - t;
|
|
return vec3(
|
|
col2.x != 0.0 ? tm * col1.x + t * col1.x / col2.x : col1.x,
|
|
col2.y != 0.0 ? tm * col1.y + t * col1.y / col2.y : col1.y,
|
|
col2.z != 0.0 ? tm * col1.z + t * col1.z / col2.z : col1.z);
|
|
}
|
|
|
|
vec3 cultmath_cycles_mix_diff(float t, vec3 col1, vec3 col2) { return col1 + t * (abs(col1 - col2) - col1); }
|
|
|
|
vec3 cultmath_cycles_mix_exclusion(float t, vec3 col1, vec3 col2)
|
|
{
|
|
return max(col1 + t * ((col1 + col2 - 2.0 * col1 * col2) - col1), vec3(0.0, 0.0, 0.0));
|
|
}
|
|
|
|
vec3 cultmath_cycles_mix_dark(float t, vec3 col1, vec3 col2) { return col1 + t * (min(col1, col2) - col1); }
|
|
|
|
vec3 cultmath_cycles_mix_light(float t, vec3 col1, vec3 col2) { return col1 + t * (max(col1, col2) - col1); }
|
|
|
|
vec3 cultmath_cycles_mix_dodge(float t, vec3 col1, vec3 col2)
|
|
{
|
|
vec3 divisor = vec3(1.0, 1.0, 1.0) - t * col2;
|
|
return vec3(
|
|
col1.x != 0.0 ? (divisor.x <= 0.0 ? 1.0 : min(col1.x / divisor.x, 1.0)) : col1.x,
|
|
col1.y != 0.0 ? (divisor.y <= 0.0 ? 1.0 : min(col1.y / divisor.y, 1.0)) : col1.y,
|
|
col1.z != 0.0 ? (divisor.z <= 0.0 ? 1.0 : min(col1.z / divisor.z, 1.0)) : col1.z);
|
|
}
|
|
|
|
vec3 cultmath_cycles_mix_burn(float t, vec3 col1, vec3 col2)
|
|
{
|
|
float tm = 1.0 - t;
|
|
vec3 divisor = tm + t * col2;
|
|
vec3 burnt = vec3(1.0, 1.0, 1.0) - (vec3(1.0, 1.0, 1.0) - col1) / divisor;
|
|
return vec3(
|
|
divisor.x <= 0.0 ? 0.0 : clamp(burnt.x, 0.0, 1.0),
|
|
divisor.y <= 0.0 ? 0.0 : clamp(burnt.y, 0.0, 1.0),
|
|
divisor.z <= 0.0 ? 0.0 : clamp(burnt.z, 0.0, 1.0));
|
|
}
|
|
|
|
// rgb_to_hsv of util/color.h
|
|
vec3 cultmath_cycles_rgb_to_hsv(vec3 rgb)
|
|
{
|
|
float cmax = max(rgb.x, max(rgb.y, rgb.z));
|
|
float cmin = min(rgb.x, min(rgb.y, rgb.z));
|
|
float cdelta = cmax - cmin;
|
|
float s = cmax != 0.0 ? cdelta / cmax : 0.0;
|
|
vec3 c = (vec3(cmax, cmax, cmax) - rgb) / cdelta;
|
|
float h = rgb.x == cmax ? c.z - c.y : (rgb.y == cmax ? 2.0 + c.x - c.z : 4.0 + c.y - c.x);
|
|
h = h * 0.16666667; // Cycles' kernel is built with reciprocal math: h /= 6.0f computes h * (1 / 6.0f).
|
|
h = h < 0.0 ? h + 1.0 : h;
|
|
return vec3(s != 0.0 ? h : 0.0, s, cmax);
|
|
}
|
|
|
|
// hsv_to_rgb of util/color.h
|
|
vec3 cultmath_cycles_hsv_to_rgb(vec3 hsv)
|
|
{
|
|
float h = hsv.x == 1.0 ? 0.0 : hsv.x;
|
|
h = h * 6.0;
|
|
float i = floor(h);
|
|
float f = h - i;
|
|
float p = hsv.z * (1.0 - hsv.y);
|
|
float q = hsv.z * (1.0 - (hsv.y * f));
|
|
float t = hsv.z * (1.0 - (hsv.y * (1.0 - f)));
|
|
vec3 rgb = i == 0.0 ? vec3(hsv.z, t, p) :
|
|
(i == 1.0 ? vec3(q, hsv.z, p) :
|
|
(i == 2.0 ? vec3(p, hsv.z, t) :
|
|
(i == 3.0 ? vec3(p, q, hsv.z) :
|
|
(i == 4.0 ? vec3(t, p, hsv.z) : vec3(hsv.z, p, q)))));
|
|
return hsv.y != 0.0 ? rgb : vec3(hsv.z, hsv.z, hsv.z);
|
|
}
|
|
|
|
vec3 cultmath_cycles_mix_hue(float t, vec3 col1, vec3 col2)
|
|
{
|
|
vec3 hsv2 = cultmath_cycles_rgb_to_hsv(col2);
|
|
vec3 hsv = cultmath_cycles_rgb_to_hsv(col1);
|
|
vec3 shifted = cultmath_cycles_hsv_to_rgb(vec3(hsv2.x, hsv.y, hsv.z));
|
|
return hsv2.y != 0.0 ? col1 + t * (shifted - col1) : col1;
|
|
}
|
|
|
|
vec3 cultmath_cycles_mix_sat(float t, vec3 col1, vec3 col2)
|
|
{
|
|
float tm = 1.0 - t;
|
|
vec3 hsv = cultmath_cycles_rgb_to_hsv(col1);
|
|
vec3 hsv2 = cultmath_cycles_rgb_to_hsv(col2);
|
|
return hsv.y != 0.0 ? cultmath_cycles_hsv_to_rgb(vec3(hsv.x, tm * hsv.y + t * hsv2.y, hsv.z)) : col1;
|
|
}
|
|
|
|
vec3 cultmath_cycles_mix_val(float t, vec3 col1, vec3 col2)
|
|
{
|
|
float tm = 1.0 - t;
|
|
vec3 hsv = cultmath_cycles_rgb_to_hsv(col1);
|
|
vec3 hsv2 = cultmath_cycles_rgb_to_hsv(col2);
|
|
return cultmath_cycles_hsv_to_rgb(vec3(hsv.x, hsv.y, tm * hsv.z + t * hsv2.z));
|
|
}
|
|
|
|
vec3 cultmath_cycles_mix_color(float t, vec3 col1, vec3 col2)
|
|
{
|
|
vec3 hsv2 = cultmath_cycles_rgb_to_hsv(col2);
|
|
vec3 hsv = cultmath_cycles_rgb_to_hsv(col1);
|
|
vec3 shifted = cultmath_cycles_hsv_to_rgb(vec3(hsv2.x, hsv2.y, hsv.z));
|
|
return hsv2.y != 0.0 ? col1 + t * (shifted - col1) : col1;
|
|
}
|
|
|
|
vec3 cultmath_cycles_mix_soft(float t, vec3 col1, vec3 col2)
|
|
{
|
|
float tm = 1.0 - t;
|
|
vec3 one = vec3(1.0, 1.0, 1.0);
|
|
vec3 scr = one - (one - col2) * (one - col1);
|
|
return tm * col1 + t * ((one - col1) * col2 * col1 + col1 * scr);
|
|
}
|
|
|
|
vec3 cultmath_cycles_mix_linear(float t, vec3 col1, vec3 col2)
|
|
{
|
|
return col1 + t * (2.0 * col2 + vec3(-1.0, -1.0, -1.0));
|
|
}
|
|
|
|
// ---- RGB to BW node (kernel/svm/convert.h NODE_CONVERT_CF) ----
|
|
|
|
float cultmath_cycles_rgb_to_bw(vec3 color)
|
|
{
|
|
return color.x * CULTMATH_CYCLES_RGB_TO_Y_R + color.y * CULTMATH_CYCLES_RGB_TO_Y_G + color.z * CULTMATH_CYCLES_RGB_TO_Y_B;
|
|
}
|
|
|
|
// ---- Color Ramp lookup (kernel/svm/ramp.h rgb_ramp_lookup, without extrapolation) ----
|
|
|
|
// Where a ramp of `size` table entries is read at t: (index, weight), the lower entry and the weight of the
|
|
// next one. The colour is table[index] when weight is 0 and (1 - weight) table[index] + weight table[index + 1]
|
|
// otherwise. The ramp table itself is the caller's; Blender's holds 257 entries, the ramp sampled at i / 256.
|
|
vec2 cultmath_cycles_ramp_index(float t, int size)
|
|
{
|
|
float f = (t > 0.0 ? (t < 1.0 ? t : 1.0) : 0.0) * float(size - 1);
|
|
int index = clamp(int(f), 0, size - 1);
|
|
float lower = float(index);
|
|
return vec2(lower, f - lower);
|
|
}
|
|
|
|
#endif
|