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.Tests/AutoGenerated/BuiltinsTests.cs b/src/ShaderGen.Tests/AutoGenerated/BuiltinsTests.cs
new file mode 100644
index 0000000..b74369c
--- /dev/null
+++ b/src/ShaderGen.Tests/AutoGenerated/BuiltinsTests.cs
@@ -0,0 +1,405 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using ShaderGen.Tests.Tools;
+using TestShaders;
+using Veldrid;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace ShaderGen.Tests.AutoGenerated
+{
+ public class BuiltinsTests
+ {
+ #region Test Configuration
+
+ ///
+ /// The skip reason, set to to enable tests in class.
+ ///
+ private const string SkipReason = null;
+
+ ///
+ /// The number of failure examples to output.
+ ///
+ private const int FailureExamples = 5;
+
+ ///
+ /// Controls the minimum mantissa when generating a floating point number (how 'small' it can go)
+ ///
+ /// To test all valid floats this should be set to -126.
+ private static readonly int MinMantissa = -3;
+
+ ///
+ /// Controls the maximum mantissa when generating a floating point number (how 'big' it can go)
+ ///
+ /// To test all valid floats this should be set to 128.
+ private static readonly int MaxMantissa = 3;
+
+ ///
+ /// The float epsilon is used to indicate how close two floats need to be to be considered approximately equal.
+ ///
+ private float FloatEpsilon = 1f;
+
+ ///
+ /// The methods to exclude from
+ ///
+ /// TODO See #78 to show why this is another reason to split ShaderBuiltins.
+ private static readonly HashSet _gpuOnly = new HashSet
+ {
+ nameof(ShaderBuiltins.Sample),
+ nameof(ShaderBuiltins.SampleGrad),
+ nameof(ShaderBuiltins.Load),
+ nameof(ShaderBuiltins.Store),
+ nameof(ShaderBuiltins.SampleComparisonLevelZero),
+ nameof(ShaderBuiltins.Discard),
+ nameof(ShaderBuiltins.ClipToTextureCoordinates),
+ nameof(ShaderBuiltins.Ddx),
+ nameof(ShaderBuiltins.DdxFine),
+ nameof(ShaderBuiltins.Ddy),
+ nameof(ShaderBuiltins.DdyFine),
+ nameof(ShaderBuiltins.InterlockedAdd)
+ };
+
+ ///
+ /// Gets the methods to test.
+ ///
+ ///
+ /// The methods to test.
+ ///
+ private IEnumerable MethodsToTest => typeof(ShaderBuiltins)
+ .GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public)
+ .Where(m => !_gpuOnly.Contains(m.Name) && !m.IsSpecialName)
+ .OrderBy(m => m.Name);
+
+ ///
+ /// The number of test iterations for each backend.
+ ///
+ private const int TestLoops = 1000;
+
+ #endregion
+
+
+ ///
+ /// The output stream for tests.
+ ///
+ private readonly ITestOutputHelper _output;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The output.
+ public BuiltinsTests(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
+ [SkippableFact(typeof(RequiredToolFeatureMissingException), Skip = SkipReason)]
+ private void TestBuiltins()
+ {
+ // Find all backends that can create a headless graphics device on this system.
+ IReadOnlyList toolChains = ToolChain.Requires(ToolFeatures.HeadlessGraphicsDevice, false);
+ if (toolChains.Count < 1)
+ {
+ throw new RequiredToolFeatureMissingException(
+ $"At least one tool chain capable of creating headless graphics devices is required for this test!");
+ }
+
+ /*
+ * Auto-generate C# code for testing methods.
+ */
+ IReadOnlyList methods = null;
+ Mappings mappings;
+ Compilation compilation;
+ using (new TestTimer(_output, () => $"Generating C# shader code to test {methods.Count} methods"))
+ {
+ // Get all the methods we wish to test
+ methods = MethodsToTest.ToArray();
+ mappings = CreateMethodTestCompilation(methods, out compilation);
+
+ // Note, you could use compilation.Emit(...) at this point to compile the auto-generated code!
+ // however, for now we'll invoke methods directly rather than executing the C# code that has been
+ // generated, as loading emitted code into a test is currently much more difficult.
+ }
+
+ // Allocate enough space to store the result sets for each backend!
+ TestSets testSets = null;
+ using (new TestTimer(
+ _output,
+ () =>
+ $"Generating random test data ({(mappings.BufferSize * testSets.TestLoops).ToMemorySize()}) for {testSets.TestLoops} iterations of {mappings.Methods} methods")
+ )
+ {
+ testSets = new TestSets(toolChains, compilation, mappings, TestLoops, MinMantissa, MaxMantissa);
+ }
+
+ /*
+ * Transpile shaders
+ */
+
+ ShaderGenerationResult generationResult;
+
+ using (new TestTimer(
+ _output,
+ t =>
+ $"Generated shader sets for {string.Join(", ", toolChains.Select(tc => tc.Name))} backends in {t * 1000:#.##}ms.")
+ )
+ {
+ ShaderGenerator sg = new ShaderGenerator(
+ compilation,
+ testSets.Select(t => t.Backend).Where(b => b != null).ToArray(),
+ null,
+ null,
+ "ComputeShader.CS");
+
+ generationResult = sg.GenerateShaders();
+ }
+
+ /*
+ * Loop through each backend to run tests.
+ */
+ bool first = true;
+ using (new TestTimer(_output, "Executing all tests on all backends"))
+ {
+ foreach (TestSet testSet in testSets)
+ {
+ _output.WriteLine(string.Empty);
+ if (first)
+ {
+ // This is the first test set, so we use Space1 instead of Spacer 2.
+ first = false;
+ _output.WriteLine(TestUtil.Spacer1);
+ }
+ else
+ {
+ _output.WriteLine(TestUtil.Spacer2);
+ }
+
+ _output.WriteLine(String.Empty);
+
+ testSet.Execute(generationResult, "CS", _output);
+ }
+
+ _output.WriteLine(string.Empty);
+ }
+
+ _output.WriteLine(string.Empty);
+ _output.WriteLine(TestUtil.Spacer1);
+ _output.WriteLine(string.Empty);
+
+ Assert.True(testSets.Count(t => t.Executed) > 1,
+ "At least 2 test sets are required for comparison.");
+
+ /*
+ * Finally, evaluate differences between results
+ */
+ IReadOnlyList<(MethodMap MethodMap, IReadOnlyList Failures)> failures;
+ using (new TestTimer(_output, "Analysing results for failures"))
+ {
+ failures = testSets.GetFailures(FloatEpsilon)
+ .Select(kvp => (MethodMap: kvp.Key, Failures: kvp.Value))
+ .OrderByDescending(kvp => kvp.Failures.Count)
+ .ToArray();
+ }
+
+ if (!failures.Any())
+ {
+ _output.WriteLine("No failures detected!");
+ return;
+ }
+
+ _output.WriteLine(
+ $"{failures.Count} methods had failures out of {mappings.Methods} ({100.0 * failures.Count / mappings.Methods:#.##}%).");
+
+ _output.WriteLine(string.Empty);
+
+ // Get pointer array
+ string lastMethodName = null;
+ foreach ((MethodMap methodMap, IReadOnlyList methodFailures) in failures)
+ {
+ if (lastMethodName != methodMap.Method.Name)
+ {
+ if (lastMethodName != null)
+ {
+ // Seperate methods of different names with spacer 1
+ _output.WriteLine(string.Empty);
+ _output.WriteLine(TestUtil.Spacer1);
+ _output.WriteLine(string.Empty);
+ }
+
+ lastMethodName = methodMap.Method.Name;
+ }
+ else
+ {
+ _output.WriteLine(string.Empty);
+ _output.WriteLine(TestUtil.Spacer2);
+ _output.WriteLine(string.Empty);
+ }
+
+ int failureCount = methodFailures.Count;
+ _output.WriteLine(
+ $"{TestUtil.GetUnicodePieChart((double)failureCount / testSets.TestLoops)} {methodMap.Signature} failed {failureCount}/{testSets.TestLoops} ({failureCount * 100.0 / testSets.TestLoops:#.##}%).");
+
+ // Output examples!
+ int example = 0;
+ foreach (Failure failure in methodFailures)
+ {
+ _output.WriteLine(string.Empty);
+ if (example++ >= FailureExamples)
+ {
+ _output.WriteLine("…");
+ break;
+ }
+
+ _output.WriteLine(failure.ToString());
+ }
+ }
+
+ _output.WriteLine(string.Empty);
+ _output.WriteLine(TestUtil.Spacer2);
+ _output.WriteLine(string.Empty);
+ }
+
+ ///
+ /// Creates the method test compilation.
+ ///
+ /// The methods.
+ /// The compilation.
+ ///
+ private Mappings CreateMethodTestCompilation(IReadOnlyCollection methods,
+ out Compilation compilation)
+ {
+ Assert.NotNull(methods);
+ Assert.NotEmpty(methods);
+
+ // Create compilation
+ CSharpCompilationOptions cSharpCompilationOptions =
+ new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true);
+ compilation = CSharpCompilation.Create(
+ "TestAssembly",
+ null,
+ TestUtil.ProjectReferences,
+ cSharpCompilationOptions);
+
+ // Temporary structure to hold method maps until after we calculate input structure.
+ var methodMaps =
+ new(int Index, MethodInfo Method, IReadOnlyDictionary Parameters, string
+ ReturnField)[methods.Count];
+
+ PaddedStructCreator paddedStructCreator = new PaddedStructCreator(compilation);
+
+ StringBuilder codeBuilder = new StringBuilder();
+ codeBuilder.Append(SBSP1);
+ codeBuilder.Append(methods.Count);
+ codeBuilder.Append(SBSP2);
+
+ StringBuilder argsBuilder = new StringBuilder();
+ /*
+ * Output test cases
+ */
+ int methodNumber = 0;
+ foreach (MethodInfo method in methods)
+ {
+ Assert.True(method.IsStatic);
+
+ ParameterInfo[] parameterInfos = method.GetParameters();
+ Dictionary parameterMap =
+ new Dictionary(parameterInfos.Length);
+
+ foreach (ParameterInfo parameterInfo in parameterInfos)
+ {
+ if (argsBuilder.Length > 0)
+ {
+ argsBuilder.Append(",");
+ }
+
+ string fieldName = paddedStructCreator.GetFieldName(parameterInfo.ParameterType);
+ parameterMap.Add(parameterInfo, fieldName);
+ argsBuilder.Append(SBSParam.Replace("$$NAME$$", fieldName));
+ }
+
+ string returnName = method.ReturnType != typeof(void)
+ ? paddedStructCreator.GetFieldName(method.ReturnType)
+ : null;
+
+ string output = returnName != null
+ ? SBSParam.Replace("$$NAME$$", returnName) + " = "
+ : string.Empty;
+
+ codeBuilder.Append(SBSCase
+ .Replace("$$CASE$$", methodNumber.ToString())
+ .Replace("$$RESULT$$", output)
+ .Replace("$$METHOD$$", $"{method.DeclaringType.FullName}.{method.Name}")
+ .Replace("$$ARGS$$", argsBuilder.ToString()));
+
+ methodMaps[methodNumber] = (methodNumber++, method, parameterMap, returnName);
+ paddedStructCreator.Reset();
+ argsBuilder.Clear();
+ }
+
+ codeBuilder.Append(SBSP3);
+
+ /*
+ * Output test fields
+ */
+ IReadOnlyList fields = paddedStructCreator.GetFields(out int bufferSize);
+ int size = 0;
+ foreach (PaddedStructCreator.Field field in fields)
+ {
+ codeBuilder.AppendLine(
+ $" // {size,3}: Alignment = {field.AlignmentInfo.ShaderAlignment} {(field.IsPaddingField ? " [PADDING}" : string.Empty)}");
+ codeBuilder.AppendLine(
+ $" {(field.IsPaddingField ? "private" : "public")} {field.Type.FullName} {field.Name};");
+ codeBuilder.AppendLine(string.Empty);
+ size += field.AlignmentInfo.ShaderSize;
+ }
+
+ Assert.Equal(size, bufferSize);
+
+ codeBuilder.Append(SBSP4);
+
+ string code = codeBuilder.ToString();
+ compilation = compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(code));
+ return new Mappings(bufferSize, fields.ToDictionary(f => f.Name), methodMaps);
+ }
+
+ #region Code Building Strings
+ private static readonly string SBSP1 = @"public class ComputeShader
+ {
+ public const uint Methods = ";
+ private static readonly string SBSP2 = @";
+
+ [ShaderGen.ResourceTestSet(0)] public ShaderGen.RWStructuredBuffer InOutParameters;
+
+ [ShaderGen.ComputeShader(1, 1, 1)]
+ public void CS()
+ {
+ int index = (int)ShaderGen.ShaderBuiltins.DispatchThreadID.X;
+ if (index >= Methods) return;
+ ComputeShaderParameters parameters = InOutParameters[index];
+ switch (index)
+ {
+";
+ private static readonly string SBSCase = @" case $$CASE$$:
+ $$RESULT$$$$METHOD$$($$ARGS$$);
+ break;
+";
+ private static readonly string SBSParam = @"parameters.$$NAME$$";
+ private static readonly string SBSP3 = @" }
+
+ InOutParameters[index] = parameters;
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe struct ComputeShaderParameters
+ {
+";
+ private static readonly string SBSP4 = @"}";
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/ShaderGen.Tests/AutoGenerated/Failure.cs b/src/ShaderGen.Tests/AutoGenerated/Failure.cs
new file mode 100644
index 0000000..6c055b1
--- /dev/null
+++ b/src/ShaderGen.Tests/AutoGenerated/Failure.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using ShaderGen.Tests.Tools;
+
+namespace ShaderGen.Tests.AutoGenerated
+{
+ ///
+ /// A failure of a tested method occurs when the results are not approximately the same for all
+ /// backends (and the CPU).
+ ///
+ internal class Failure
+ {
+ ///
+ /// The method map
+ ///
+ public readonly MethodMap MethodMap;
+
+ ///
+ /// The parameters
+ ///
+ public readonly IReadOnlyList