From 1c136880aa07a96f794c1b6db1da9fa812593822 Mon Sep 17 00:00:00 2001 From: mm00 Date: Thu, 27 Mar 2025 19:19:45 +0100 Subject: [PATCH] Using SIMD acceleration, added core usage limit --- SDFMapCreator/Program.cs | 83 ++++++++++++++++-------------- SDFMapCreator/SDFMapCreator.csproj | 25 +++++++++ 2 files changed, 68 insertions(+), 40 deletions(-) diff --git a/SDFMapCreator/Program.cs b/SDFMapCreator/Program.cs index 8a43e28..416a2c9 100644 --- a/SDFMapCreator/Program.cs +++ b/SDFMapCreator/Program.cs @@ -2,7 +2,11 @@ using System.Numerics; using System.Runtime.CompilerServices; using ImageMagick; +using float2 = System.Numerics.Vector2; +using float3 = System.Numerics.Vector3; +using float4 = System.Numerics.Vector4; +/* public struct float2(float x, float y) { public float x = x, y = y; } @@ -17,7 +21,7 @@ public struct float4(float r, float g, float b, float a) { public float r = r, g = g, b = b, a = a; public float4(float value) : this(value, value, value, value) {} public float4(float r, float g, float b) : this(r, g, b, 1f) {} -} +}*/ public record ImageData(MagickImage Image, float3[,] Pixels, List Edges); public record MaskData(float3[,] Mask, ImageData A, List Edges); @@ -39,26 +43,29 @@ public static class ArrayExt { float[] result = new float[array.GetLength(0) * array.GetLength(1) * 3]; for(int x = 0; x < array.GetLength(0); x++) { for(int y = 0; y < array.GetLength(1); y++) { - result[x*array.GetLength(1)*3 + y*3] = array[x, y].r; - result[x*array.GetLength(1)*3 + y*3+1] = array[x, y].g; - result[x*array.GetLength(1)*3 + y*3+2] = array[x, y].b; + result[x*array.GetLength(1)*3 + y*3] = array[x, y].X; + result[x*array.GetLength(1)*3 + y*3+1] = array[x, y].Y; + result[x*array.GetLength(1)*3 + y*3+2] = array[x, y].Z; } } return result; } } -public class Program { +public class Program { private const float MAX = 65535f; private const float MIN = 0f; - private const bool outputMasks = false; - private const bool outputSDFs = false; - private const bool outputGradients = false; + private static readonly int MAX_THREADS = Environment.ProcessorCount - 2; + private const bool outputMasks = true; + private const bool outputSDFs = true; + private const bool outputGradients = true; static List Images = new(); static List Masks = new(); static List SDFs = new(); static List Gradients = new(); + static readonly ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = MAX_THREADS }; + static void LoadImage(string imgPath) { var image = new MagickImage(imgPath); float3[,] pixels; @@ -79,14 +86,10 @@ public class Program { Console.WriteLine("Reading images..."); //foreach image in arguments load the image - LoadImage("01.png"); - LoadImage("02.png"); - LoadImage("03.png"); - LoadImage("04.png"); - LoadImage("05.png"); - LoadImage("06.png"); - LoadImage("07.png"); - LoadImage("08.png"); + var imagesPath = "images"; + + for (int i = 0; i < 8; i++) + LoadImage($"./{imagesPath}{Path.DirectorySeparatorChar}{i+1:00}.png"); //LoadImage("1.png"); //LoadImage("2.png"); @@ -149,8 +152,8 @@ public class Program { var transition = new float3[width, height]; for (var x = 0; x < mask.Mask.GetLength(0); x++) { for (var y = 0; y < mask.Mask.GetLength(1); y++) { - if (mask.Mask[x, y].r == 0) continue; - transition[x, y] = new(Lerp(Images[i].Pixels[x,y].r, MAX, gradient[x,y].r)); + if (mask.Mask[x, y].X == 0) continue; + transition[x, y] = new(Lerp(Images[i].Pixels[x,y].X, MAX, gradient[x,y].X)); } } var transitionImage = new MagickImage(MagickColors.Black, (uint)mask.Mask.GetLength(0), (uint)mask.Mask.GetLength(1)); @@ -168,18 +171,18 @@ public class Program { var sw = new Stopwatch(); sw.Start(); Console.WriteLine("Running edge detection..."); - Parallel.For(0, width * height, (i) => { + Parallel.For(0, width * height, parallelOptions, (i) => { int x = (int)(i % width); int y = (int)(i / width); if (!EdgeKernel(maskData.Mask, x, y, width, height)) return; var color = maskData.Mask[x, y]; - color.g = MAX; + color.Y = MAX; maskData.Mask[x, y] = color; lock(maskData.Edges) maskData.Edges.Add(new(x, y)); iterCount++; if (iterCount % (width * height / 100) == 0) { - Console.WriteLine($"Progress: {iterCount/(width*height):P}% | {iterCount/(sw.Elapsed.TotalSeconds):N0} pixels/s"); + Console.WriteLine($"Progress: {iterCount/(float)(width*height):P} | {iterCount/(sw.Elapsed.TotalSeconds):N0} pixels/s"); } }); sw.Stop(); @@ -194,20 +197,20 @@ public class Program { sw.Start(); float3[,] temp = new float3[width, height]; Console.WriteLine("Running edge detection..."); - Parallel.For(0, width * height, (i) => { + Parallel.For(0, width * height, parallelOptions, (i) => { int x = (int)(i % width); int y = (int)(i / width); - var a = (sdfA.SDF[x,y].r + sdfB.SDF[x,y].g + sdfB.SDF[x,y].b )/ 3; - var b = (sdfB.SDF[x,y].r + sdfB.SDF[x,y].g + sdfB.SDF[x,y].b )/ 3; + var a = (sdfA.SDF[x,y].X + sdfB.SDF[x,y].Y + sdfB.SDF[x,y].Z )/ 3; + var b = (sdfB.SDF[x,y].X + sdfB.SDF[x,y].Y + sdfB.SDF[x,y].Z )/ 3; var gradient = a / (a + b); temp[x, y] = new(Remap(gradient, 0, 1, MIN, MAX)); }); - Console.WriteLine($"Gradient Time: {sw.Elapsed.TotalSeconds:N4}s ({iterCount/sw.Elapsed.TotalSeconds:N0} pixels/s)"); - var min = temp.Cast().Min(x => x.r); - var max = temp.Cast().Max(x => x.r); + Console.WriteLine($"Gradient Time: {sw.Elapsed.TotalSeconds:N4}s ({iterCount/(float)sw.Elapsed.TotalSeconds:N0} pixels/s)"); + var min = temp.Cast().Min(x => x.X); + var max = temp.Cast().Max(x => x.X); Console.WriteLine($"Min: {min} | Max: {max}"); return temp; } @@ -220,7 +223,7 @@ public class Program { int iterCount = 0; var sw = new Stopwatch(); sw.Start(); - Parallel.For(0, width * height, (i) => { + Parallel.For(0, width * height, parallelOptions, (i) => { //convert 1D index to 2D index var x = (int)(i % width); var y = (int)(i / width); @@ -230,7 +233,7 @@ public class Program { //loop through all the pixels in the mask foreach (var edge in mask.Edges) { - float dist = EuclideanDistance(p, edge); + float dist = float2.DistanceSquared(p, edge); if (dist < minDist) minDist = dist; } @@ -238,17 +241,17 @@ public class Program { if (minDist > AbsMax) AbsMax = minDist; iterCount++; if (iterCount % (width * height / 100) == 0) { - Console.WriteLine($"Progress: {iterCount/(width*height):P}% | {iterCount/(sw.Elapsed.TotalSeconds):N0} pixels/s"); + Console.WriteLine($"Progress: {iterCount/(float)(width*height):P} | {iterCount/(sw.Elapsed.TotalSeconds):N0} pixels/s"); } }); Console.WriteLine($"SDF Generation Time: {sw.Elapsed.TotalSeconds:N4}s ({iterCount/sw.Elapsed.TotalSeconds:N0} pixels/s)"); sw.Restart(); - Parallel.For(0, width * height, (i) => { + Parallel.For(0, width * height, parallelOptions, (i) => { //convert 1D index to 2D index var x = (int)(i % width); var y = (int)(i / width); - temp[x, y] = new(Remap(temp[x, y].r, 0, AbsMax, MIN, MAX)); + temp[x, y] = new(Remap(temp[x, y].X, 0, AbsMax, MIN, MAX)); }); Console.WriteLine($"SDF Normalization Time: {sw.Elapsed.TotalSeconds:N4}s ({iterCount/sw.Elapsed.TotalSeconds:N0} pixels/s)"); Console.WriteLine("AbsMax: " + AbsMax); @@ -257,7 +260,7 @@ public class Program { [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float EuclideanDistance(float2 a, float2 b) => - MathF.Sqrt(MathF.Pow(a.x - b.x, 2) + MathF.Pow(a.y - b.y, 2)); + MathF.Sqrt(MathF.Pow(a.X - b.X, 2) + MathF.Pow(a.Y - b.Y, 2)); private static void ImageData(MagickImage image1, IPixelCollection pixels1) { @@ -272,13 +275,13 @@ public class Program { static float3[,] GetABMask(float3[,] A, float3[,] B, uint resX, uint resY) { var temp = new float3[resX, resY]; - Parallel.For(0, resX*resY, (i) => { + Parallel.For(0, resX*resY, parallelOptions, (i) => { uint x = (uint)(i % resX); uint y = (uint)(i / resX); var pixelA = A[x, y]; var pixelB = B[x, y]; - float lumaA = (pixelA.r+pixelA.g+pixelA.b)/3; - float lumaB = (pixelB.r+pixelB.g+pixelB.b)/3; + float lumaA = (pixelA.X+pixelA.Y+pixelA.Z)/3; + float lumaB = (pixelB.X+pixelB.Y+pixelB.Z)/3; float resultPixel = lumaB > lumaA ? MAX : MIN; temp[x, y] = new(resultPixel, 0, 0); }); @@ -287,11 +290,11 @@ public class Program { static float3[,] SelfMask(float3[,] A, uint resX, uint resY) { var temp = new float3[resX, resY]; - Parallel.For(0, resX*resY, (i) => { + Parallel.For(0, resX*resY, parallelOptions, (i) => { uint x = (uint)(i % resX); uint y = (uint)(i / resX); var pixelA = A[x, y]; - float lumaA = (pixelA.r+pixelA.g+pixelA.b)/3; + float lumaA = (pixelA.X+pixelA.Y+pixelA.Z)/3; float resultPixel = lumaA > 0 ? MAX : MIN; temp[x, y] = new(resultPixel, 0, 0); }); @@ -300,7 +303,7 @@ public class Program { static bool EdgeKernel(float3[,] mask, int x, int y, uint width, uint height) { //if we are already empty, return false - if (mask[x, y].r == 0) return false; + if (mask[x, y].X == 0) return false; //if we are on the edge of the image, return false if (x == 0 || y == 0 || x == width - 1 || y == height - 1) return false; //check the 3x3 kernel @@ -308,7 +311,7 @@ public class Program { for (int yi = y - 1; yi <= y + 1; yi++) { if (xi < 0 || xi >= width || yi < 0 || yi >= height) continue; //skip out of bounds pixels - if (mask[xi, yi].r == 0) + if (mask[xi, yi].X == 0) return true; //if we find a black pixel, return true } } diff --git a/SDFMapCreator/SDFMapCreator.csproj b/SDFMapCreator/SDFMapCreator.csproj index 2307201..2d047f2 100644 --- a/SDFMapCreator/SDFMapCreator.csproj +++ b/SDFMapCreator/SDFMapCreator.csproj @@ -9,6 +9,7 @@ + @@ -21,6 +22,30 @@ PreserveNewest + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always +