From f9a7b579aac7753e4e5ce9f1c1e4b319125cfbbe Mon Sep 17 00:00:00 2001 From: Craig Dean Date: Tue, 5 Jun 2018 23:34:10 +0100 Subject: [PATCH] Added new 'AddCheck' methods to known functions implementations. This makes it easier to implement a check that runs for each element in a vector. Added ShaderBuiltins.FMod. Mod made consistent across backends (as distinct from FMod), by implementing correctly in HLSL (and CPU). Made Pow and Cbrt implementations consistent by using Abs of 1st parameter. This is how Vulkan does it, whereas the other backends all return NaN. As we can't consistently return NaN on backends, I've adopted Vulkan's approach. Corrected CPU Clamp, Lerp and SmoothStep implementations by removing checks (makes consistent with GPU implementations). Corrected CPU Clamp implementation. --- src/ShaderGen.Primitives/ShaderBuiltins.cs | 49 ++++++++---- src/ShaderGen/Glsl/Glsl330KnownFunctions.cs | 80 ++++++++++++++----- src/ShaderGen/Glsl/Glsl450KnownFunctions.cs | 38 ++++++++- src/ShaderGen/Glsl/GlslEs300KnownFunctions.cs | 80 ++++++++++++++----- src/ShaderGen/Hlsl/HlslKnownFunctions.cs | 62 +++++++++++--- src/ShaderGen/Metal/MetalKnownFunctions.cs | 39 ++++++++- 6 files changed, 275 insertions(+), 73 deletions(-) diff --git a/src/ShaderGen.Primitives/ShaderBuiltins.cs b/src/ShaderGen.Primitives/ShaderBuiltins.cs index 7a10fa5..50fc056 100644 --- a/src/ShaderGen.Primitives/ShaderBuiltins.cs +++ b/src/ShaderGen.Primitives/ShaderBuiltins.cs @@ -1,5 +1,6 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; namespace ShaderGen { @@ -100,6 +101,7 @@ public static float SampleComparisonLevelZero(DepthTexture2DArrayResource textur public static Vector4 Acos(Vector4 value) => new Vector4((float)Math.Acos(value.X), (float)Math.Acos(value.Y), (float)Math.Acos(value.Z), (float)Math.Acos(value.W)); // Acosh + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Acosh(float value) => (float)Math.Log(value + Math.Sqrt(value * value - 1.0)); public static Vector2 Acosh(Vector2 value) => new Vector2(Acosh(value.X), Acosh(value.Y)); public static Vector3 Acosh(Vector3 value) => new Vector3(Acosh(value.X), Acosh(value.Y), Acosh(value.Z)); @@ -112,6 +114,7 @@ public static float SampleComparisonLevelZero(DepthTexture2DArrayResource textur public static Vector4 Asin(Vector4 value) => new Vector4((float)Math.Asin(value.X), (float)Math.Asin(value.Y), (float)Math.Asin(value.Z), (float)Math.Asin(value.W)); // Asinh + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Asinh(float value) => (float)Math.Log(value + Math.Sqrt(value * value + 1.0)); public static Vector2 Asinh(Vector2 value) => new Vector2(Asinh(value.X), Asinh(value.Y)); public static Vector3 Asinh(Vector3 value) => new Vector3(Asinh(value.X), Asinh(value.Y), Asinh(value.Z)); @@ -128,6 +131,7 @@ public static float SampleComparisonLevelZero(DepthTexture2DArrayResource textur public static Vector4 Atan(Vector4 y, Vector4 x) => new Vector4((float)Math.Atan2(y.X, x.X), (float)Math.Atan2(y.Y, x.Y), (float)Math.Atan2(y.Z, x.Z), (float)Math.Atan2(y.W, x.W)); // Atanh + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Atanh(float value) => (float)(Math.Log((1.0f + value) / (1.0f - value)) / 2.0f); public static Vector2 Atanh(Vector2 value) => new Vector2(Atanh(value.X), Atanh(value.Y)); public static Vector3 Atanh(Vector3 value) => new Vector3(Atanh(value.X), Atanh(value.Y), Atanh(value.Z)); @@ -135,7 +139,8 @@ public static float SampleComparisonLevelZero(DepthTexture2DArrayResource textur // Cbrt TODO add Matrix support private const double _third = 1.0 / 3.0; - public static float Cbrt(float value) => (float)Math.Pow(value, _third); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Cbrt(float value) => (float)Math.Pow(Math.Abs(value), _third); public static Vector2 Cbrt(Vector2 value) => new Vector2(Cbrt(value.X), Cbrt(value.Y)); public static Vector3 Cbrt(Vector3 value) => new Vector3(Cbrt(value.X), Cbrt(value.Y), Cbrt(value.Z)); public static Vector4 Cbrt(Vector4 value) => new Vector4(Cbrt(value.X), Cbrt(value.Y), Cbrt(value.Z), Cbrt(value.W)); @@ -147,9 +152,8 @@ public static float SampleComparisonLevelZero(DepthTexture2DArrayResource textur public static Vector4 Ceiling(Vector4 value) => new Vector4((float)Math.Ceiling(value.X), (float)Math.Ceiling(value.Y), (float)Math.Ceiling(value.Z), (float)Math.Ceiling(value.W)); // Clamp TODO add int & uint versions (see https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/clamp.xhtml) - public static float Clamp(float value, float min, float max) => min >= max - ? float.NaN - : Math.Min(Math.Max(value, min), max); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Clamp(float value, float min, float max) => Math.Min(Math.Max(value, min), max); public static Vector2 Clamp(Vector2 value, Vector2 min, Vector2 max) => new Vector2(Clamp(value.X, min.X, max.X), Clamp(value.Y, min.Y, max.Y)); public static Vector2 Clamp(Vector2 value, float min, float max) => new Vector2(Clamp(value.X, min, max), Clamp(value.Y, min, max)); public static Vector3 Clamp(Vector3 value, Vector3 min, Vector3 max) => new Vector3(Clamp(value.X, min.X, max.X), Clamp(value.Y, min.Y, max.Y), Clamp(value.Z, min.Z, max.Z)); @@ -181,14 +185,27 @@ public static float Clamp(float value, float min, float max) => min >= max public static Vector3 Floor(Vector3 value) => new Vector3((float)Math.Floor(value.X), (float)Math.Floor(value.Y), (float)Math.Floor(value.Z)); public static Vector4 Floor(Vector4 value) => new Vector4((float)Math.Floor(value.X), (float)Math.Floor(value.Y), (float)Math.Floor(value.Z), (float)Math.Floor(value.W)); + + // FMod - See https://stackoverflow.com/questions/7610631/glsl-mod-vs-hlsl-fmod + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float FMod(float a, float b) => a % b; + public static Vector2 FMod(Vector2 a, Vector2 b) => new Vector2(FMod(a.X, b.X), FMod(a.Y, b.Y)); + public static Vector2 FMod(Vector2 a, float b) => new Vector2(FMod(a.X, b), FMod(a.Y, b)); + public static Vector3 FMod(Vector3 a, Vector3 b) => new Vector3(FMod(a.X, b.X), FMod(a.Y, b.Y), FMod(a.Z, b.Z)); + public static Vector3 FMod(Vector3 a, float b) => new Vector3(FMod(a.X, b), FMod(a.Y, b), FMod(a.Z, b)); + public static Vector4 FMod(Vector4 a, Vector4 b) => new Vector4(FMod(a.X, b.X), FMod(a.Y, b.Y), FMod(a.Z, b.Z), FMod(a.W, b.W)); + public static Vector4 FMod(Vector4 a, float b) => new Vector4(FMod(a.X, b), FMod(a.Y, b), FMod(a.Z, b), FMod(a.W, b)); + // Frac TODO Check this really is equivalent + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Frac(float value) => (float)(value - Math.Floor(value)); public static Vector2 Frac(Vector2 value) => new Vector2(Frac(value.X), Frac(value.Y)); public static Vector3 Frac(Vector3 value) => new Vector3(Frac(value.X), Frac(value.Y), Frac(value.Z)); public static Vector4 Frac(Vector4 value) => new Vector4(Frac(value.X), Frac(value.Y), Frac(value.Z), Frac(value.W)); // Lerp - public static float Lerp(float x, float y, float s) => s < 0f || s > 1f ? float.NaN : x * (1f - s) + y * s; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Lerp(float x, float y, float s) => x * (1f - s) + y * s; public static Vector2 Lerp(Vector2 x, Vector2 y, Vector2 s) => new Vector2(Lerp(x.X, y.X, s.X), Lerp(x.Y, y.Y, s.Y)); public static Vector2 Lerp(Vector2 x, Vector2 y, float s) => new Vector2(Lerp(x.X, y.X, s), Lerp(x.Y, y.Y, s)); public static Vector3 Lerp(Vector3 x, Vector3 y, Vector3 s) => new Vector3(Lerp(x.X, y.X, s.X), Lerp(x.Y, y.Y, s.Y), Lerp(x.Z, y.Z, s.Z)); @@ -198,6 +215,7 @@ public static float Clamp(float value, float min, float max) => min >= max // Log public static float Log(float value) => (float)Math.Log(value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Log(float a, float newBase) => (float)Math.Log(a, newBase); public static Vector2 Log(Vector2 value) => new Vector2((float)Math.Log(value.X), (float)Math.Log(value.Y)); public static Vector2 Log(Vector2 a, Vector2 newBase) => new Vector2(Log(a.X, newBase.X), Log(a.Y, newBase.Y)); @@ -210,6 +228,7 @@ public static float Clamp(float value, float min, float max) => min >= max public static Vector4 Log(Vector4 a, float newBase) => new Vector4(Log(a.X, newBase), Log(a.Y, newBase), Log(a.Z, newBase), Log(a.W, newBase)); // Log2 + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Log2(float value) => Log(value, 2f); public static Vector2 Log2(Vector2 value) => new Vector2(Log2(value.X), Log2(value.Y)); public static Vector3 Log2(Vector3 value) => new Vector3(Log2(value.X), Log2(value.Y), Log2(value.Z)); @@ -247,8 +266,9 @@ public static float Clamp(float value, float min, float max) => min >= max m.M14 * v.X + m.M24 * v.Y + m.M34 * v.Z + m.M44 * v.W ); - // Mod TODO: See https://stackoverflow.com/questions/7610631/glsl-mod-vs-hlsl-fmod - public static float Mod(float a, float b) => a % b; // CHECK! + // Mod - See https://stackoverflow.com/questions/7610631/glsl-mod-vs-hlsl-fmod + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Mod(float a, float b) => a - b * (float)Math.Floor(a / b); public static Vector2 Mod(Vector2 a, Vector2 b) => new Vector2(Mod(a.X, b.X), Mod(a.Y, b.Y)); public static Vector2 Mod(Vector2 a, float b) => new Vector2(Mod(a.X, b), Mod(a.Y, b)); public static Vector3 Mod(Vector3 a, Vector3 b) => new Vector3(Mod(a.X, b.X), Mod(a.Y, b.Y), Mod(a.Z, b.Z)); @@ -257,10 +277,11 @@ public static float Clamp(float value, float min, float max) => min >= max public static Vector4 Mod(Vector4 a, float b) => new Vector4(Mod(a.X, b), Mod(a.Y, b), Mod(a.Z, b), Mod(a.W, b)); // Pow - public static float Pow(float x, float y) => (float)Math.Pow(x, y); - public static Vector2 Pow(Vector2 y, Vector2 x) => new Vector2((float)Math.Pow(y.X, x.X), (float)Math.Pow(y.Y, x.Y)); - public static Vector3 Pow(Vector3 y, Vector3 x) => new Vector3((float)Math.Pow(y.X, x.X), (float)Math.Pow(y.Y, x.Y), (float)Math.Pow(y.Z, x.Z)); - public static Vector4 Pow(Vector4 y, Vector4 x) => new Vector4((float)Math.Pow(y.X, x.X), (float)Math.Pow(y.Y, x.Y), (float)Math.Pow(y.Z, x.Z), (float)Math.Pow(y.W, x.W)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Pow(float x, float y) => (float)Math.Pow(Math.Abs(x), y); + public static Vector2 Pow(Vector2 y, Vector2 x) => new Vector2((float)Pow(y.X, x.X), Pow(y.Y, x.Y)); + public static Vector3 Pow(Vector3 y, Vector3 x) => new Vector3(Pow(y.X, x.X), Pow(y.Y, x.Y), Pow(y.Z, x.Z)); + public static Vector4 Pow(Vector4 y, Vector4 x) => new Vector4(Pow(y.X, x.X), Pow(y.Y, x.Y), Pow(y.Z, x.Z), Pow(y.W, x.W)); // Round public static float Round(float value) => (float)Math.Round(value); @@ -293,6 +314,7 @@ public static float Clamp(float value, float min, float max) => min >= max public static Vector4 Sqrt(Vector4 value) => new Vector4((float)Math.Sqrt(value.X), (float)Math.Sqrt(value.Y), (float)Math.Sqrt(value.Z), (float)Math.Sqrt(value.W)); // SmoothStep + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float SmoothStep(float min, float max, float x) { // From https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/smoothstep.xhtml @@ -301,11 +323,6 @@ public static float SmoothStep(float min, float max, float x) * return t * t * (3.0 - 2.0 * t); * Results are undefined if min ≥ max. */ - if (min >= max) - { - return float.NaN; - } - float t = Saturate((x - min) / (max - min)); return t * t * (3f - 2f * t); } diff --git a/src/ShaderGen/Glsl/Glsl330KnownFunctions.cs b/src/ShaderGen/Glsl/Glsl330KnownFunctions.cs index 5caea68..6545a82 100644 --- a/src/ShaderGen/Glsl/Glsl330KnownFunctions.cs +++ b/src/ShaderGen/Glsl/Glsl330KnownFunctions.cs @@ -23,11 +23,11 @@ private static Dictionary GetMappings() { nameof(ShaderBuiltins.Acosh), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Asin), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Asinh), SimpleNameTranslator() }, - { nameof(ShaderBuiltins.Atan), SimpleNameTranslator() },// Note atan supports both (x) and (y,x) + { nameof(ShaderBuiltins.Atan), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Atanh), SimpleNameTranslator() }, - { nameof(ShaderBuiltins.Cbrt), CubeRoot }, // We can calculate the 1/3rd power, which might not give exactly the same result? + { nameof(ShaderBuiltins.Cbrt), CubeRoot }, { nameof(ShaderBuiltins.Ceiling), SimpleNameTranslator("ceil") }, - { nameof(ShaderBuiltins.Clamp), SimpleNameTranslator() }, + { nameof(ShaderBuiltins.Clamp), Clamp }, { nameof(ShaderBuiltins.ClipToTextureCoordinates), ClipToTextureCoordinates }, { nameof(ShaderBuiltins.Cos), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Cosh), SimpleNameTranslator() }, @@ -39,6 +39,7 @@ private static Dictionary GetMappings() { nameof(ShaderBuiltins.DispatchThreadID), DispatchThreadID }, { nameof(ShaderBuiltins.Exp), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Floor), SimpleNameTranslator() }, + { nameof(ShaderBuiltins.FMod), FMod }, { nameof(ShaderBuiltins.Frac), SimpleNameTranslator("fract") }, { nameof(ShaderBuiltins.GroupThreadID), GroupThreadID }, { nameof(ShaderBuiltins.InstanceID), InstanceID }, @@ -51,10 +52,9 @@ private static Dictionary GetMappings() { nameof(ShaderBuiltins.Log10), Log10 }, { nameof(ShaderBuiltins.Max), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Min), SimpleNameTranslator() }, - // Potential BUG: https://stackoverflow.com/questions/7610631/glsl-mod-vs-hlsl-fmod { nameof(ShaderBuiltins.Mod), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Mul), MatrixMul }, - { nameof(ShaderBuiltins.Pow), SimpleNameTranslator() }, + { nameof(ShaderBuiltins.Pow), Pow }, { nameof(ShaderBuiltins.Round), Round }, { nameof(ShaderBuiltins.Sample), Sample }, { nameof(ShaderBuiltins.SampleComparisonLevelZero), SampleComparisonLevelZero }, @@ -205,7 +205,7 @@ private static Dictionary GetMappings() { "Log10", Log10 }, { "Max", SimpleNameTranslator() }, { "Min", SimpleNameTranslator() }, - { "Pow", SimpleNameTranslator() }, + { "Pow", Pow }, { "Round", Round }, { "Sin", SimpleNameTranslator() }, { "Sinh", SimpleNameTranslator() }, @@ -535,19 +535,6 @@ private static void GetVectorTypeInfo(string name, out string shaderType, out in else { throw new ShaderGenerationException("VectorCtor translator was called on an invalid type: " + name); } } - private static string CubeRoot(string typeName, string methodName, InvocationParameterInfo[] parameters) - { - string pType = parameters[0].FullTypeName; - if (pType == "System.Single" || pType == "float") // TODO Why are we getting float? - { - return $"pow({parameters[0].Identifier}, 0.333333333333333)"; - } - - GetVectorTypeInfo(pType, out string shaderType, out int elementCount); - return - $"pow({parameters[0].Identifier}, {shaderType}({string.Join(",", Enumerable.Range(0, elementCount).Select(i => "0.333333333333333"))}))"; - } - private static string Log(string typeName, string methodName, InvocationParameterInfo[] parameters) { if (parameters.Length < 2) @@ -593,5 +580,60 @@ private static string Round(string typeName, string methodName, InvocationParame // Round(Single, MidpointRounding) throw new NotImplementedException(); } + + private static string CubeRoot(string typeName, string methodName, InvocationParameterInfo[] parameters) + { + return AddCheck(parameters[0].FullTypeName, + $"pow(abs({parameters[0].Identifier}`), 0.333333333333333)"); + } + + private static string Pow(string typeName, string methodName, InvocationParameterInfo[] parameters) + { + // OpenGL returns NaN for -ve P0's, whereas Vulkan ignores sign. + return AddCheck(parameters[0].FullTypeName, + $"pow(abs({parameters[0].Identifier}`),{parameters[1].Identifier}`)"); + } + + private static string Clamp(string typeName, string methodName, InvocationParameterInfo[] parameters) + { + // D3D & Vulkan return Max when max < min, but OpenGL returns Min, so we need + // to correct by returning Max when max < min. + bool isFloat = parameters[1].FullTypeName == "System.Single" || parameters[1].FullTypeName == "float"; + string p1 = $"{parameters[1].Identifier}{(isFloat ? string.Empty : "`")}"; + string p2 = $"{parameters[2].Identifier}{(isFloat ? string.Empty : "`")}"; + return AddCheck(parameters[0].FullTypeName, + $"({p1}<{p2}?clamp({parameters[0].Identifier}`,{p1},{p2}):{p2})"); + } + + private static string FMod(string typeName, string methodName, InvocationParameterInfo[] parameters) + { + // D3D & Vulkan return Max when max < min, but OpenGL returns Min, so we need + // to correct by returning Max when max < min. + bool isFloat = parameters[1].FullTypeName == "System.Single" || parameters[1].FullTypeName == "float"; + string p0 = $"{parameters[0].Identifier}`"; + string p1 = $"{parameters[1].Identifier}{(isFloat ? string.Empty : "`")}"; + return AddCheck(parameters[0].FullTypeName, + $"({p0}-{p1}*trunc({p0}/{p1}))"); + } + + private static readonly string[] _vectorAccessors = { "x", "y", "z", "w" }; + /// + /// Implements a check for each element of a vector. + /// + /// Name of the type. + /// The check. + /// + private static string AddCheck(string typeName, string check) + { + if (typeName == "System.Single" || typeName == "float") // TODO Why are we getting float? + { + // The check can stay as it is, strip the '`' characters. + return check.Replace("`", string.Empty); + } + + GetVectorTypeInfo(typeName, out string shaderType, out int elementCount); + return + $"{shaderType}({string.Join(",", _vectorAccessors.Take(elementCount).Select(a => check.Replace("`", "." + a)))})"; + } } } diff --git a/src/ShaderGen/Glsl/Glsl450KnownFunctions.cs b/src/ShaderGen/Glsl/Glsl450KnownFunctions.cs index 3b94f00..ce2e014 100644 --- a/src/ShaderGen/Glsl/Glsl450KnownFunctions.cs +++ b/src/ShaderGen/Glsl/Glsl450KnownFunctions.cs @@ -23,9 +23,9 @@ private static Dictionary GetMappings() { nameof(ShaderBuiltins.Acosh), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Asin), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Asinh), SimpleNameTranslator() }, - { nameof(ShaderBuiltins.Atan), SimpleNameTranslator() },// Note atan supports both (x) and (y,x) + { nameof(ShaderBuiltins.Atan), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Atanh), SimpleNameTranslator() }, - { nameof(ShaderBuiltins.Cbrt), CubeRoot }, // We can calculate the 1/3rd power, which might not give exactly the same result? + { nameof(ShaderBuiltins.Cbrt), CubeRoot }, { nameof(ShaderBuiltins.Ceiling), SimpleNameTranslator("ceil") }, { nameof(ShaderBuiltins.Clamp), SimpleNameTranslator() }, { nameof(ShaderBuiltins.ClipToTextureCoordinates), ClipToTextureCoordinates }, @@ -39,6 +39,7 @@ private static Dictionary GetMappings() { nameof(ShaderBuiltins.DispatchThreadID), DispatchThreadID }, { nameof(ShaderBuiltins.Exp), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Floor), SimpleNameTranslator() }, + { nameof(ShaderBuiltins.FMod), FMod }, { nameof(ShaderBuiltins.Frac), SimpleNameTranslator("fract") }, { nameof(ShaderBuiltins.GroupThreadID), GroupThreadID }, { nameof(ShaderBuiltins.InstanceID), InstanceID }, @@ -51,7 +52,6 @@ private static Dictionary GetMappings() { nameof(ShaderBuiltins.Log10), Log10 }, { nameof(ShaderBuiltins.Max), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Min), SimpleNameTranslator() }, - // Potential BUG: https://stackoverflow.com/questions/7610631/glsl-mod-vs-hlsl-fmod { nameof(ShaderBuiltins.Mod), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Mul), MatrixMul }, { nameof(ShaderBuiltins.Pow), SimpleNameTranslator() }, @@ -565,6 +565,7 @@ private static string CubeRoot(string typeName, string methodName, InvocationPar } GetVectorTypeInfo(pType, out string shaderType, out int elementCount); + // TODO All backends but Vulkan return NaN for Cbrt of a -ve number... return $"pow({parameters[0].Identifier}, {shaderType}({string.Join(",", Enumerable.Range(0, elementCount).Select(i => "0.333333333333333"))}))"; } @@ -614,5 +615,36 @@ private static string Round(string typeName, string methodName, InvocationParame // Round(Single, MidpointRounding) throw new NotImplementedException(); } + + private static string FMod(string typeName, string methodName, InvocationParameterInfo[] parameters) + { + // D3D & Vulkan return Max when max < min, but OpenGL returns Min, so we need + // to correct by returning Max when max < min. + bool isFloat = parameters[1].FullTypeName == "System.Single" || parameters[1].FullTypeName == "float"; + string p0 = $"{parameters[0].Identifier}`"; + string p1 = $"{parameters[1].Identifier}{(isFloat ? string.Empty : "`")}"; + return AddCheck(parameters[0].FullTypeName, + $"({p0}-{p1}*trunc({p0}/{p1}))"); + } + + private static readonly string[] _vectorAccessors = { "x", "y", "z", "w" }; + /// + /// Implements a check for each element of a vector. + /// + /// Name of the type. + /// The check. + /// + private static string AddCheck(string typeName, string check) + { + if (typeName == "System.Single" || typeName == "float") // TODO Why are we getting float? + { + // The check can stay as it is, strip the '`' characters. + return check.Replace("`", string.Empty); + } + + GetVectorTypeInfo(typeName, out string shaderType, out int elementCount); + return + $"{shaderType}({string.Join(",", _vectorAccessors.Take(elementCount).Select(a => check.Replace("`", "." + a)))})"; + } } } diff --git a/src/ShaderGen/Glsl/GlslEs300KnownFunctions.cs b/src/ShaderGen/Glsl/GlslEs300KnownFunctions.cs index 88ffdeb..9e6bd21 100644 --- a/src/ShaderGen/Glsl/GlslEs300KnownFunctions.cs +++ b/src/ShaderGen/Glsl/GlslEs300KnownFunctions.cs @@ -23,11 +23,11 @@ private static Dictionary GetMappings() { nameof(ShaderBuiltins.Acosh), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Asin), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Asinh), SimpleNameTranslator() }, - { nameof(ShaderBuiltins.Atan), SimpleNameTranslator() },// Note atan supports both (x) and (y,x) + { nameof(ShaderBuiltins.Atan), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Atanh), SimpleNameTranslator() }, - { nameof(ShaderBuiltins.Cbrt), CubeRoot }, // We can calculate the 1/3rd power, which might not give exactly the same result? + { nameof(ShaderBuiltins.Cbrt), CubeRoot }, { nameof(ShaderBuiltins.Ceiling), SimpleNameTranslator("ceil") }, - { nameof(ShaderBuiltins.Clamp), SimpleNameFloatParameterTranslator() }, + { nameof(ShaderBuiltins.Clamp), Clamp }, { nameof(ShaderBuiltins.ClipToTextureCoordinates), ClipToTextureCoordinates }, { nameof(ShaderBuiltins.Cos), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Cosh), SimpleNameTranslator() }, @@ -39,6 +39,7 @@ private static Dictionary GetMappings() { nameof(ShaderBuiltins.DispatchThreadID), DispatchThreadID }, { nameof(ShaderBuiltins.Exp), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Floor), SimpleNameTranslator() }, + { nameof(ShaderBuiltins.FMod), FMod }, { nameof(ShaderBuiltins.Frac), SimpleNameTranslator("fract") }, { nameof(ShaderBuiltins.GroupThreadID), GroupThreadID }, { nameof(ShaderBuiltins.InstanceID), InstanceID }, @@ -51,10 +52,9 @@ private static Dictionary GetMappings() { nameof(ShaderBuiltins.Log10), Log10 }, { nameof(ShaderBuiltins.Max), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Min), SimpleNameTranslator() }, - // Potential BUG: https://stackoverflow.com/questions/7610631/glsl-mod-vs-hlsl-fmod { nameof(ShaderBuiltins.Mod), SimpleNameFloatParameterTranslator() }, { nameof(ShaderBuiltins.Mul), MatrixMul }, - { nameof(ShaderBuiltins.Pow), SimpleNameFloatParameterTranslator() }, + { nameof(ShaderBuiltins.Pow), Pow }, { nameof(ShaderBuiltins.Round), Round }, { nameof(ShaderBuiltins.Sample), Sample }, { nameof(ShaderBuiltins.SampleComparisonLevelZero), SampleComparisonLevelZero }, @@ -205,7 +205,7 @@ private static Dictionary GetMappings() { "Log10", Log10 }, { "Max", SimpleNameTranslator() }, { "Min", SimpleNameTranslator() }, - { "Pow", SimpleNameFloatParameterTranslator() }, + { "Pow", Pow }, { "Round", Round }, { "Sin", SimpleNameTranslator() }, { "Sinh", SimpleNameTranslator() }, @@ -557,19 +557,6 @@ private static void GetVectorTypeInfo(string name, out string shaderType, out in else { throw new ShaderGenerationException("VectorCtor translator was called on an invalid type: " + name); } } - private static string CubeRoot(string typeName, string methodName, InvocationParameterInfo[] parameters) - { - string pType = parameters[0].FullTypeName; - if (pType == "System.Single" || pType == "float") // TODO Why are we getting float? - { - return $"pow({parameters[0].Identifier}, 0.333333333333333)"; - } - - GetVectorTypeInfo(pType, out string shaderType, out int elementCount); - return - $"pow({parameters[0].Identifier}, {shaderType}({string.Join(",", Enumerable.Range(0, elementCount).Select(i => "0.333333333333333"))}))"; - } - private static string Log(string typeName, string methodName, InvocationParameterInfo[] parameters) { if (parameters.Length < 2) @@ -615,5 +602,60 @@ private static string Round(string typeName, string methodName, InvocationParame // Round(Single, MidpointRounding) throw new NotImplementedException(); } + + private static string CubeRoot(string typeName, string methodName, InvocationParameterInfo[] parameters) + { + return AddCheck(parameters[0].FullTypeName, + $"pow(abs({parameters[0].Identifier}`), 0.333333333333333)"); + } + + private static string Pow(string typeName, string methodName, InvocationParameterInfo[] parameters) + { + // OpenGL returns NaN for -ve P0's, whereas Vulkan ignores sign. + return AddCheck(parameters[0].FullTypeName, + $"pow(abs({parameters[0].Identifier}`),{parameters[1].Identifier}`)"); + } + + private static string Clamp(string typeName, string methodName, InvocationParameterInfo[] parameters) + { + // D3D & Vulkan return Max when max < min, but OpenGL returns Min, so we need + // to correct by returning Max when max < min. + bool isFloat = parameters[1].FullTypeName == "System.Single" || parameters[1].FullTypeName == "float"; + string p1 = $"{parameters[1].Identifier}{(isFloat ? string.Empty : "`")}"; + string p2 = $"{parameters[2].Identifier}{(isFloat ? string.Empty : "`")}"; + return AddCheck(parameters[0].FullTypeName, + $"(({p1}<{p2})?(clamp({parameters[0].Identifier}`,{p1},{p2})):({p2}))"); + } + + private static string FMod(string typeName, string methodName, InvocationParameterInfo[] parameters) + { + // D3D & Vulkan return Max when max < min, but OpenGL returns Min, so we need + // to correct by returning Max when max < min. + bool isFloat = parameters[1].FullTypeName == "System.Single" || parameters[1].FullTypeName == "float"; + string p0 = $"{parameters[0].Identifier}`"; + string p1 = $"{parameters[1].Identifier}{(isFloat ? string.Empty : "`")}"; + return AddCheck(parameters[0].FullTypeName, + $"({p0}-{p1}*trunc({p0}/{p1}))"); + } + + private static readonly string[] _vectorAccessors = { "x", "y", "z", "w" }; + /// + /// Implements a check for each element of a vector. + /// + /// Name of the type. + /// The check. + /// + private static string AddCheck(string typeName, string check) + { + if (typeName == "System.Single" || typeName == "float") // TODO Why are we getting float? + { + // The check can stay as it is, strip the '`' characters. + return check.Replace("`", string.Empty); + } + + GetVectorTypeInfo(typeName, out string shaderType, out int elementCount); + return + $"{shaderType}({string.Join(",", _vectorAccessors.Take(elementCount).Select(a => check.Replace("`", "." + a)))})"; + } } } diff --git a/src/ShaderGen/Hlsl/HlslKnownFunctions.cs b/src/ShaderGen/Hlsl/HlslKnownFunctions.cs index b2661e4..7a9d1f5 100644 --- a/src/ShaderGen/Hlsl/HlslKnownFunctions.cs +++ b/src/ShaderGen/Hlsl/HlslKnownFunctions.cs @@ -23,9 +23,9 @@ private static Dictionary GetMappings() { nameof(ShaderBuiltins.Acosh), Acosh }, { nameof(ShaderBuiltins.Asin), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Asinh), Asinh }, - { nameof(ShaderBuiltins.Atan), Atan },// Note atan supports both (x) and (y,x) + { nameof(ShaderBuiltins.Atan), Atan }, { nameof(ShaderBuiltins.Atanh), Atanh }, - { nameof(ShaderBuiltins.Cbrt), CubeRoot }, // We can calculate the 1/3rd power, which might not give exactly the same result? + { nameof(ShaderBuiltins.Cbrt), CubeRoot }, { nameof(ShaderBuiltins.Ceiling), SimpleNameTranslator("ceil") }, { nameof(ShaderBuiltins.Clamp), SimpleNameTranslator() }, { nameof(ShaderBuiltins.ClipToTextureCoordinates), ClipToTextureCoordinates }, @@ -39,6 +39,7 @@ private static Dictionary GetMappings() { nameof(ShaderBuiltins.DispatchThreadID), DispatchThreadID }, { nameof(ShaderBuiltins.Exp), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Floor), SimpleNameTranslator() }, + { nameof(ShaderBuiltins.FMod), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Frac), SimpleNameTranslator() }, { nameof(ShaderBuiltins.GroupThreadID), GroupThreadID }, { nameof(ShaderBuiltins.InstanceID), InstanceID }, @@ -51,10 +52,9 @@ private static Dictionary GetMappings() { nameof(ShaderBuiltins.Log10), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Max), SimpleNameTranslator() }, { nameof(ShaderBuiltins.Min), SimpleNameTranslator() }, - // Potential BUG: https://stackoverflow.com/questions/7610631/glsl-mod-vs-hlsl-fmod - { nameof(ShaderBuiltins.Mod), SimpleNameTranslator("fmod") }, + { nameof(ShaderBuiltins.Mod), Mod }, { nameof(ShaderBuiltins.Mul), SimpleNameTranslator() }, - { nameof(ShaderBuiltins.Pow), SimpleNameTranslator() }, + { nameof(ShaderBuiltins.Pow), Pow }, { nameof(ShaderBuiltins.Round), Round }, { nameof(ShaderBuiltins.Sample), Sample }, { nameof(ShaderBuiltins.SampleComparisonLevelZero), SampleComparisonLevelZero }, @@ -205,7 +205,7 @@ private static Dictionary GetMappings() { "Log10", SimpleNameTranslator() }, { "Max", SimpleNameTranslator() }, { "Min", SimpleNameTranslator() }, - { "Pow", SimpleNameTranslator() }, + { "Pow", Pow }, { "Round", Round }, { "Sin", SimpleNameTranslator() }, { "Sinh", SimpleNameTranslator() }, @@ -531,12 +531,6 @@ private static string Cvc(string typeName, float value) return $"{shaderType}({string.Join(",", Enumerable.Range(0, elementCount).Select(i => v))})"; } - private static string CubeRoot(string typeName, string methodName, InvocationParameterInfo[] parameters) - { - InvocationParameterInfo firstParameter = parameters[0]; - return $"pow({firstParameter.Identifier}, {Cvc(firstParameter.FullTypeName, 0.333333333333333f)})"; - } - private static string Log(string typeName, string methodName, InvocationParameterInfo[] parameters) { if (parameters.Length < 2) @@ -605,6 +599,50 @@ private static string Atanh(string typeName, string methodName, InvocationParame return $"log(({Cvc(firstParameter.FullTypeName, 1f)}+{target})/({Cvc(firstParameter.FullTypeName, 1f)}-{target})/{Cvc(firstParameter.FullTypeName, 2f)})"; } + + private static string CubeRoot(string typeName, string methodName, InvocationParameterInfo[] parameters) + { + return AddCheck(parameters[0].FullTypeName, + $"pow(abs({parameters[0].Identifier}`), 0.333333333333333)"); + } + + private static string Pow(string typeName, string methodName, InvocationParameterInfo[] parameters) + { + // Direct 3D returns NaN for -ve P0's, whereas Vulkan ignores sign. + return AddCheck(parameters[0].FullTypeName, + $"pow(abs({parameters[0].Identifier}`),{parameters[1].Identifier}`)"); + } + + private static string Mod(string typeName, string methodName, InvocationParameterInfo[] parameters) + { + // D3D & Vulkan return Max when max < min, but OpenGL returns Min, so we need + // to correct by returning Max when max < min. + bool isFloat = parameters[1].FullTypeName == "System.Single" || parameters[1].FullTypeName == "float"; + string p0 = $"{parameters[0].Identifier}`"; + string p1 = $"{parameters[1].Identifier}{(isFloat ? string.Empty : "`")}"; + return AddCheck(parameters[0].FullTypeName, + $"({p0}-{p1}*floor({p0}/{p1}))"); + } + + private static readonly string[] _vectorAccessors = { "x", "y", "z", "w" }; + /// + /// Implements a check for each element of a vector. + /// + /// Name of the type. + /// The check. + /// + private static string AddCheck(string typeName, string check) + { + if (typeName == "System.Single" || typeName == "float") // TODO Why are we getting float? + { + // The check can stay as it is, strip the '`' characters. + return check.Replace("`", string.Empty); + } + + GetVectorTypeInfo(typeName, out string shaderType, out int elementCount); + return + $"{shaderType}({string.Join(",", _vectorAccessors.Take(elementCount).Select(a => check.Replace("`", "." + a)))})"; + } } public delegate string InvocationTranslator(string typeName, string methodName, InvocationParameterInfo[] parameters); diff --git a/src/ShaderGen/Metal/MetalKnownFunctions.cs b/src/ShaderGen/Metal/MetalKnownFunctions.cs index 9c8f252..2efb2c0 100644 --- a/src/ShaderGen/Metal/MetalKnownFunctions.cs +++ b/src/ShaderGen/Metal/MetalKnownFunctions.cs @@ -23,9 +23,9 @@ private static Dictionary GetMappings() {nameof(ShaderBuiltins.Acosh), SimpleNameTranslator()}, {nameof(ShaderBuiltins.Asin), SimpleNameTranslator()}, {nameof(ShaderBuiltins.Asinh), SimpleNameTranslator()}, - {nameof(ShaderBuiltins.Atan), Atan}, // Note atan supports both (x) and (y,x) + {nameof(ShaderBuiltins.Atan), Atan}, {nameof(ShaderBuiltins.Atanh), SimpleNameTranslator()}, - {nameof(ShaderBuiltins.Cbrt), CubeRoot}, // We can calculate the 1/3rd power, which might not give exactly the same result? + {nameof(ShaderBuiltins.Cbrt), CubeRoot}, {nameof(ShaderBuiltins.Ceiling), SimpleNameTranslator("ceil")}, {nameof(ShaderBuiltins.Clamp), Clamp}, {nameof(ShaderBuiltins.ClipToTextureCoordinates), ClipToTextureCoordinates}, @@ -39,6 +39,7 @@ private static Dictionary GetMappings() {nameof(ShaderBuiltins.DispatchThreadID), DispatchThreadID}, {nameof(ShaderBuiltins.Exp), SimpleNameTranslator()}, {nameof(ShaderBuiltins.Floor), SimpleNameTranslator()}, + {nameof(ShaderBuiltins.FMod), FMod }, {nameof(ShaderBuiltins.Frac), SimpleNameTranslator("fract")}, {nameof(ShaderBuiltins.GroupThreadID), GroupThreadID}, {nameof(ShaderBuiltins.InstanceID), InstanceID}, @@ -51,7 +52,6 @@ private static Dictionary GetMappings() {nameof(ShaderBuiltins.Log10), Log10}, {nameof(ShaderBuiltins.Max), SimpleNameTranslator()}, {nameof(ShaderBuiltins.Min), SimpleNameTranslator()}, - // Potential BUG: https://stackoverflow.com/questions/7610631/glsl-mod-vs-hlsl-fmod {nameof(ShaderBuiltins.Mod), SimpleNameTranslator("fmod")}, {nameof(ShaderBuiltins.Mul), MatrixMul}, {nameof(ShaderBuiltins.Pow), Pow}, @@ -673,7 +673,7 @@ private static string Atan(string typeName, string methodName, InvocationParamet return parameters[0].FullTypeName.Contains("Vector") ? $"atan2({InvocationParameterInfo.GetInvocationParameterList(parameters)})" - : $"atan2({parameters[0].Identifier}, (float){parameters[1].Identifier})" ; + : $"atan2({parameters[0].Identifier}, (float){parameters[1].Identifier})"; } private static string Pow(string typeName, string methodName, InvocationParameterInfo[] parameters) @@ -687,5 +687,36 @@ private static string Pow(string typeName, string methodName, InvocationParamete ? $"pow({InvocationParameterInfo.GetInvocationParameterList(parameters)})" : $"pow({parameters[0].Identifier}, (float){parameters[1].Identifier})"; } + + private static string FMod(string typeName, string methodName, InvocationParameterInfo[] parameters) + { + // D3D & Vulkan return Max when max < min, but OpenGL returns Min, so we need + // to correct by returning Max when max < min. + bool isFloat = parameters[1].FullTypeName == "System.Single" || parameters[1].FullTypeName == "float"; + string p0 = $"{parameters[0].Identifier}`"; + string p1 = $"{parameters[1].Identifier}{(isFloat ? string.Empty : "`")}"; + return AddCheck(parameters[0].FullTypeName, + $"({p0}-{p1}*trunc({p0}/{p1}))"); + } + + private static readonly string[] _vectorAccessors = { "x", "y", "z", "w" }; + /// + /// Implements a check for each element of a vector. + /// + /// Name of the type. + /// The check. + /// + private static string AddCheck(string typeName, string check) + { + if (typeName == "System.Single" || typeName == "float") // TODO Why are we getting float? + { + // The check can stay as it is, strip the '`' characters. + return check.Replace("`", string.Empty); + } + + GetVectorTypeInfo(typeName, out string shaderType, out int elementCount); + return + $"{shaderType}({string.Join(",", _vectorAccessors.Take(elementCount).Select(a => check.Replace("`", "." + a)))})"; + } } }