Match step's NaN result to what dxc emits

dxc 1.9.0.5402 (dxc_2026_07_29, lib_6_3, -HV 2021, -Od) lowers step(e, x) to
fcmp olt x, e; select 0 : 1, so NaN on either side gives 1. The previous
x >= e ? 1 : 0 rule came from prose, not the compiler. math.step and
cultmath_step now use x < e ? 0 : 1 for every overload; the mirror's
cultmath_step compiles to the same fcmp/select as the intrinsic. Unity shader
copy synced; DLL rebuilt and byte-checked by build-unity-package.ps1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-14 14:09:49 +02:00
commit e0a054a7b4
7 changed files with 8 additions and 8 deletions

View file

@ -40,8 +40,8 @@ That makes these HLSL rules, not options:
- Scalar/vector arithmetic and comparisons have explicit overloads on both
sides rather than leaning on implicit scalar splat.
- Intrinsics return HLSL's types and edge cases: `sign` returns `int`/`intN`
(NaN gives 0); `step(y, x)` is `x >= y ? 1 : 0` (NaN gives 0), in C# and in the
HLSL mirror; `any`/`all` accept numeric vectors (a component is true when it is
(NaN gives 0); `step(y, x)` is `x < y ? 0 : 1` (NaN gives 1), which is how dxc
lowers it, in C# and in the HLSL mirror; `any`/`all` accept numeric vectors (a component is true when it is
not 0); `min`, `max`, and `abs` on int vectors return int vectors.
Deferred until a consumer needs them: `float4x4`, `transpose`, `determinant`,

View file

@ -36,7 +36,7 @@ float2 cultmath_lerp(float2 start, float2 end, float2 amount) { return start + (
float3 cultmath_lerp(float3 start, float3 end, float3 amount) { return start + (end - start) * amount; }
float4 cultmath_lerp(float4 start, float4 end, float4 amount) { return start + (end - start) * amount; }
float cultmath_step(float edge, float value) { return value >= edge ? 1.0 : 0.0; }
float cultmath_step(float edge, float value) { return value < edge ? 0.0 : 1.0; }
float2 cultmath_step(float2 edge, float2 value) { return float2(cultmath_step(edge.x, value.x), cultmath_step(edge.y, value.y)); }
float3 cultmath_step(float3 edge, float3 value) { return float3(cultmath_step(edge.x, value.x), cultmath_step(edge.y, value.y), cultmath_step(edge.z, value.z)); }
float4 cultmath_step(float4 edge, float4 value) { return float4(cultmath_step(edge.x, value.x), cultmath_step(edge.y, value.y), cultmath_step(edge.z, value.z), cultmath_step(edge.w, value.w)); }

View file

@ -150,8 +150,8 @@ public static partial class math
public static double2 unlerp(double2 start, double2 end, double2 value) => (value - start) / (end - start);
public static double3 unlerp(double3 start, double3 end, double3 value) => (value - start) / (end - start);
// HLSL: step(y, x) = x >= y ? 1 : 0, so a NaN on either side yields 0.
public static float step(float edge, float value) => value >= edge ? 1.0f : 0.0f;
// dxc lowers step(y, x) to x < y ? 0 : 1 (fcmp olt, then select), so a NaN on either side yields 1.
public static float step(float edge, float value) => value < edge ? 0.0f : 1.0f;
public static float2 step(float2 edge, float2 value) => new(step(edge.x, value.x), step(edge.y, value.y));
public static float3 step(float3 edge, float3 value) => new(step(edge.x, value.x), step(edge.y, value.y), step(edge.z, value.z));
public static float4 step(float4 edge, float4 value) => new(step(edge.x, value.x), step(edge.y, value.y), step(edge.z, value.z), step(edge.w, value.w));

View file

@ -161,8 +161,8 @@ public sealed class HlslSemanticsTests
Assert.Equal(new int4(1, 2, 0, 3), math.abs(new int4(-1, 2, 0, -3)));
Assert.Equal(7, math.abs(-7));
Assert.Equal(0.0f, math.step(1.0f, float.NaN));
Assert.Equal(0.0f, math.step(float.NaN, 1.0f));
Assert.Equal(1.0f, math.step(1.0f, float.NaN));
Assert.Equal(1.0f, math.step(float.NaN, 1.0f));
Assert.Equal(1.0f, math.step(1.0f, 1.0f));
Assert.Equal(0, math.sign(float.NaN));

View file

@ -36,7 +36,7 @@ float2 cultmath_lerp(float2 start, float2 end, float2 amount) { return start + (
float3 cultmath_lerp(float3 start, float3 end, float3 amount) { return start + (end - start) * amount; }
float4 cultmath_lerp(float4 start, float4 end, float4 amount) { return start + (end - start) * amount; }
float cultmath_step(float edge, float value) { return value >= edge ? 1.0 : 0.0; }
float cultmath_step(float edge, float value) { return value < edge ? 0.0 : 1.0; }
float2 cultmath_step(float2 edge, float2 value) { return float2(cultmath_step(edge.x, value.x), cultmath_step(edge.y, value.y)); }
float3 cultmath_step(float3 edge, float3 value) { return float3(cultmath_step(edge.x, value.x), cultmath_step(edge.y, value.y), cultmath_step(edge.z, value.z)); }
float4 cultmath_step(float4 edge, float4 value) { return float4(cultmath_step(edge.x, value.x), cultmath_step(edge.y, value.y), cultmath_step(edge.z, value.z), cultmath_step(edge.w, value.w)); }