using NUnit.Framework; using System; using UnityEngine.PathTracing.Core; using UnityEngine.Rendering; namespace UnityEngine.PathTracing.Tests { internal class SegmentedReductionTests { SegmentedReduction reduction; [SetUp] public void SetUp() { reduction = new SegmentedReduction(SegmentedReduction.LoadShader()); } [Test] [TestCase(1u, 64u, 64u)] // Test some small problem size [TestCase(3u, 64u, 64u)] [TestCase(7u, 64u, 64u)] [TestCase(1u, 1697u, 1201u)] // Test some prime numbers - these are the tricky cases [TestCase(3u, 1697u, 1201u)] [TestCase(7u, 1697u, 1201u)] [TestCase(1u, 193u, 43201u)] // Test some large prime numbers for size [TestCase(3u, 193u, 43201u)] [TestCase(7u, 193u, 43201u)] [TestCase(1u, 43201u, 193u)] // ... and for number of sums [TestCase(3u, 43201u, 193u)] [TestCase(7u, 43201u, 193u)] public void SegmentedReduction_WithAnyData_MatchesReferenceImplementation(uint stride, uint numSums, uint sumSize) { // Generate some random data to sum System.Random r = new System.Random(1337); float[] sums = new float[numSums * sumSize * stride]; for (uint i = 0; i < sums.Length; i++) { sums[i] = (float)r.NextDouble() * 10.0f; } // Super simple manual sum on CPU float[] referenceSums = new float[numSums * stride]; for (uint i = 0; i < numSums; i++) { for (uint j = 0; j < sumSize; j++) { for (int k = 0; k < stride; k++) { referenceSums[i * stride + k] += sums[(i * sumSize + j) * stride + k]; } } } // Now sum on GPU using var bufferToSum = new GraphicsBuffer(GraphicsBuffer.Target.Structured, sums.Length, sizeof(float)); bufferToSum.SetData(sums); uint scratchSize = SegmentedReduction.GetScratchBufferSizeInDwords(sumSize, stride, numSums); using var scratchBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, (int)scratchSize, sizeof(float)); using var outputBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, referenceSums.Length, sizeof(float)); using var cmd = new CommandBuffer(); reduction.TwoPassSegmentedReduction(cmd, sumSize, stride, numSums, 0, 0, bufferToSum, scratchBuffer, outputBuffer, true); Graphics.ExecuteCommandBuffer(cmd); float[] gpuSums = new float[referenceSums.Length]; outputBuffer.GetData(gpuSums); // Compare results for (uint i = 0; i < referenceSums.Length; i++) { float difference = Math.Abs(referenceSums[i] - gpuSums[i]); float error = difference / referenceSums[i]; // Floating point addition is not associative, so we can't expect perfect results. // We allow for a maximum error of 0.01%. Assert.Less(error, 0.0001f, $"Value at {i} didn't match reference result!"); } } } }