From 1d64749e7671ee12f0bb8084cfd540830f5da503 Mon Sep 17 00:00:00 2001 From: Samuele Lorefice Date: Wed, 2 Apr 2025 19:54:38 +0200 Subject: [PATCH] Started converting the methods to parallel processing enabled kernels --- SDFMapCreator/Program.cs | 104 +++++++------------------ SDFMapCreator/Records.cs | 6 +- SDFMapCreator/SdfKernels.Kernels.cs | 52 +++++++++++++ SDFMapCreator/SdfKernels.cs | 115 ++++++++++++++++++++++++++++ 4 files changed, 198 insertions(+), 79 deletions(-) create mode 100644 SDFMapCreator/SdfKernels.Kernels.cs create mode 100644 SDFMapCreator/SdfKernels.cs diff --git a/SDFMapCreator/Program.cs b/SDFMapCreator/Program.cs index 67f484b..d9b6281 100644 --- a/SDFMapCreator/Program.cs +++ b/SDFMapCreator/Program.cs @@ -16,20 +16,20 @@ public class Program { static List TransitionMasks = new(); static List SDFs = new(); static List Gradients = new(); - + static readonly SdfKernels kernels = new(); static void ConsoleUpdateLine(string s) => Console.Write("\r" + s); public static void Main(string[] args) { Console.WriteLine("Reading images..."); - + var imagesPath = "images"; - + for (int i = 0; i < 8; i++) { var pixels = ImageUtil.LoadImage($"./{imagesPath}{Path.DirectorySeparatorChar}{i + 1:00}.png"); Images.Add(new(pixels, pixels.GetLength(0), pixels.GetLength(1))); } - + /* var pixels = ImageUtil.LoadImage($"./sphereempty.png"); Images.Add(new(pixels, pixels.GetLength(0), pixels.GetLength(1))); @@ -40,7 +40,7 @@ public class Program { pixels = ImageUtil.LoadImage($"./spherefull.png"); Images.Add(new(pixels, pixels.GetLength(0), pixels.GetLength(1))); */ - + //check if all the images in Images are the same resolution if (Images.Select(img => (img.Width, img.Height)).Distinct().Count() > 1) { @@ -48,18 +48,15 @@ public class Program { Environment.Exit(1); } - for (int i = 1; i < Images.Count; i++) - { - for (int x = 0; x < Images[i].Width; x++) - { - for (int y = 0; y < Images[i].Height; y++) - { + for (int i = 1; i < Images.Count; i++) { + for (int x = 0; x < Images[i].Width; x++) { + for (int y = 0; y < Images[i].Height; y++) { Images[i].Pixels[x, y].X = MathF.Min(Images[i - 1].Pixels[x, y].X + Images[i].Pixels[x, y].X, MAX); Images[i].Pixels[x, y].Y = MathF.Min(Images[i - 1].Pixels[x, y].Y + Images[i].Pixels[x, y].X, MAX); Images[i].Pixels[x, y].Z = MathF.Min(Images[i - 1].Pixels[x, y].Z + Images[i].Pixels[x, y].X, MAX); } } - + Images[i].Pixels.SaveImage($"./Sum{i}.png"); } @@ -104,20 +101,21 @@ public class Program { var finalImage = new Vector3[width, height]; var currStep = 0f; var stepIncrement = MAX / (Gradients.Count); - - for(int x=0; x {currStep + stepIncrement}"); for (var x = 0; x < mask.Mask.GetLength(0); x++) { for (var y = 0; y < mask.Mask.GetLength(1); y++) { - if(gradient[x, y].X == 0) continue; - var pixel = new Vector3(Remap(gradient[x, y].X, MIN, MAX, 1.0f-currStep, 1.0f-(currStep + stepIncrement))); - if(pixel.X > finalImage[x, y].X) finalImage[x, y] = pixel; + if (gradient[x, y].X == 0) continue; + var pixel = new Vector3(Remap(gradient[x, y].X, MIN, MAX, 1.0f - currStep, + 1.0f - (currStep + stepIncrement))); + if (pixel.X > finalImage[x, y].X) finalImage[x, y] = pixel; } } currStep += stepIncrement; @@ -129,29 +127,18 @@ public class Program { private static void EdgeDetect(MaskData maskData) { uint width = (uint)maskData.Image.Width; uint height = (uint)maskData.Image.Height; - int iterCount = 0; var sw = new Stopwatch(); sw.Start(); Console.WriteLine("Running edge detection..."); - 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.Y = MAX; - maskData.Mask[x, y] = color; - lock (maskData.Edges) maskData.Edges.Add(new(x, y)); - iterCount++; - if (iterCount % (width * height / 100) == 0) { - ConsoleUpdateLine( - $"Progress: {iterCount / (float)(width * height):P} | {iterCount / (sw.Elapsed.TotalSeconds):N0} pixels/s"); - } + kernels.EdgeDetect(maskData.Mask, out var temp); + maskData.Mask = temp; + Parallel.For(0, width * height, parallelOptions, (i) => { + if(maskData.Mask[i % width, i / width].Y != 0) lock (maskData.Edges) maskData.Edges.Add(new(i % width, i / width)); }); + sw.Stop(); Console.WriteLine( - $"\nEdge pixels: {maskData.Edges.Count} | {maskData.Edges.Count / (sw.ElapsedMilliseconds+1)} pixels/s\n Time: {sw.Elapsed.TotalSeconds:F4}s"); + $"\nEdge pixels: {maskData.Edges.Count} | {maskData.Edges.Count / (sw.ElapsedMilliseconds + 1)} pixels/s\n Time: {sw.Elapsed.TotalSeconds:F4}s"); } static Vector3[,] Gradient(TransitionMaskData mask, SDFData sdfA, SDFData sdfB) { @@ -202,7 +189,7 @@ public class Program { //convert 1D index to 2D index var x = (int)(i % width); var y = (int)(i / width); - Vector2 p = new(x/(float)width, y/(float)height); //get the pixel position as a Vector2 + Vector2 p = new(x / (float)width, y / (float)height); //get the pixel position as a Vector2 float minDist = MAX + 1; //initialize the minimum distance to the maximum possible value @@ -246,56 +233,17 @@ public class Program { [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float EuclideanDistance(Vector2 a, Vector2 b) => MathF.Sqrt(MathF.Pow(a.X - b.X, 2) + MathF.Pow(a.Y - b.Y, 2)); - - + static Vector3[,] GetABMask(Vector3[,] A, Vector3[,] B, uint resX, uint resY) { - var temp = new Vector3[resX, resY]; - 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.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); - }); + kernels.ABMask(A, B, out var temp); return temp; } static Vector3[,] SelfMask(Vector3[,] A, uint resX, uint resY) { - var temp = new Vector3[resX, resY]; - 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.X + pixelA.Y + pixelA.Z) / 3; - float resultPixel = lumaA > 0.99 ? MAX : MIN; - temp[x, y] = new(resultPixel, 0, 0); - }); + kernels.SelfMask(A, out var temp); return temp; } - static bool EdgeKernel(Vector3[,] mask, int x, int y, uint width, uint height) { - //if we are already empty, 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 - for (int xi = x - 1; xi <= x + 1; xi++) { - 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].X == 0) - return true; //if we find a black pixel, return true - } - } - //if we didn't find any black pixels, return false - return false; - } - static T Lerp(T a, T b, float t) where T : INumber, IMultiplyOperators, IAdditionOperators => a * (1 - t) + b * t; diff --git a/SDFMapCreator/Records.cs b/SDFMapCreator/Records.cs index eb7eef0..45c06ad 100644 --- a/SDFMapCreator/Records.cs +++ b/SDFMapCreator/Records.cs @@ -3,6 +3,10 @@ using System.Numerics; namespace SDFMapCreator; public record Image(Vector3[,] Pixels, int Width, int Height); -public record MaskData(Vector3[,] Mask, Image Image, List Edges); +public record MaskData(Vector3[,] Mask, Image Image, List Edges) { + public Vector3[,] Mask { get; set; } = Mask; + public List Edges { get; set; } = Edges; +} + public record SDFData(Vector3[,] SDF); public record TransitionMaskData(Vector3[,] Mask, Image ImageA, Image ImageB); \ No newline at end of file diff --git a/SDFMapCreator/SdfKernels.Kernels.cs b/SDFMapCreator/SdfKernels.Kernels.cs new file mode 100644 index 0000000..0da0cd2 --- /dev/null +++ b/SDFMapCreator/SdfKernels.Kernels.cs @@ -0,0 +1,52 @@ +using System.Numerics; +using ILGPU; +using ILGPU.Runtime; + +namespace SDFMapCreator; + +public partial class SdfKernels { + private static void SelfMaskKernel(Index2D index, ArrayView2D input, ArrayView2D mask) { + int x = index.X; + int y = index.Y; + Vector3 value = input[x, y]; + float lumaA = (value.X + value.Y + value.Z)/3f; + float r = lumaA > 0.99f ? 1f : 0f; + mask[x,y] = new (r, 0f, 0f); + } + + private static void ABMaskKernel(Index2D index, + ArrayView2D A, + ArrayView2D B, + ArrayView2D mask + ) { + int x = index.X; + int y = index.Y; + Vector3 valueA = A[x, y]; + Vector3 valueB = B[x, y]; + float lumaA = (valueA.X + valueA.Y + valueA.Z) / 3f; + float lumaB = (valueB.X + valueB.Y + valueB.Z) / 3f; + float r = lumaA > 0.99f && lumaB > 0.99f ? 1f : 0f; + mask[x, y] = new (r, 0f, 0f); + } + + private static void EdgeKernel(Index2D index, + ArrayView2D mask, + uint width, uint height + ) { // early exit if not on mask + if (mask[index].X == 0f) return; + int x = index.X; + int y = index.Y; + //if we are on the edge of the image, return false + if (x == 0 || y == 0 || x == width - 1 || y == height - 1) return; + + //check the 3x3 kernel + for (int xi = x - 1; xi <= x + 1; xi++) { + 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].X == 0f) + mask[index].Y = 1f; //if we find a black pixel, return true + } + } + } +} \ No newline at end of file diff --git a/SDFMapCreator/SdfKernels.cs b/SDFMapCreator/SdfKernels.cs new file mode 100644 index 0000000..a2f2246 --- /dev/null +++ b/SDFMapCreator/SdfKernels.cs @@ -0,0 +1,115 @@ +using System; +using System.Numerics; +using ILGPU; +using ILGPU.Runtime; +using ILGPU.Runtime.CPU; +using ILGPU.Runtime.Cuda; +using ILGPU.Runtime.OpenCL; + +namespace SDFMapCreator; + +public partial class SdfKernels { + private Context gpuContext; + + public SdfKernels() { + // Initialize the GPU context + gpuContext = Context.Create(builder => builder + .OpenCL() + .Cuda() + .CPU() + ); + + Console.WriteLine("Reading available accelerators (CUDA only)..."); + foreach (var device in gpuContext.GetCudaDevices()) { + using Accelerator accelerator = device.CreateAccelerator(gpuContext); + Console.WriteLine($"{GetInfoString(accelerator)}"); + } + Console.WriteLine("Reading available accelerators (OpenCL only)..."); + foreach (var device in gpuContext.GetCLDevices()) { + using Accelerator accelerator = device.CreateAccelerator(gpuContext); + Console.WriteLine($"{GetInfoString(accelerator)}"); + } + Console.WriteLine("Reading available accelerators (CPU only)..."); + foreach (var device in gpuContext.GetCPUDevices()) { + using Accelerator accelerator = device.CreateAccelerator(gpuContext); + Console.WriteLine($"{GetInfoString(accelerator)}"); + } + + } + + public void SelfMask(Vector3[,] input, out Vector3[,] mask) { + var dev = gpuContext.GetPreferredDevice(preferCPU:false); + int width = input.GetLength(0); + int height = input.GetLength(1); + mask = new Vector3[width, height]; + using Accelerator accelerator = dev.CreateAccelerator(gpuContext); + + using var buffer = accelerator.Allocate2DDenseX(new (width, height)); + buffer.CopyFromCPU(input); + + using var maskBuffer = accelerator.Allocate2DDenseX(new (width, height)); + + var selfMaskKernel = accelerator.LoadAutoGroupedStreamKernel, + ArrayView2D>(SelfMaskKernel); + + selfMaskKernel(new (width, height), buffer.View, maskBuffer.View); + + accelerator.Synchronize(); + + mask = maskBuffer.GetAsArray2D(); + } + + public void ABMask(Vector3[,] A, Vector3[,] B, out Vector3[,] mask) { + var dev = gpuContext.GetPreferredDevice(preferCPU:false); + int width = A.GetLength(0); + int height = A.GetLength(1); + mask = new Vector3[width, height]; + using Accelerator accelerator = dev.CreateAccelerator(gpuContext); + + using var bufferA = accelerator.Allocate2DDenseX(new (width, height)); + bufferA.CopyFromCPU(A); + + using var bufferB = accelerator.Allocate2DDenseX(new (width, height)); + bufferB.CopyFromCPU(B); + + using var maskBuffer = accelerator.Allocate2DDenseX(new (width, height)); + + var abMaskKernel = accelerator.LoadAutoGroupedStreamKernel, + ArrayView2D, + ArrayView2D>(ABMaskKernel); + + abMaskKernel(new (width, height), bufferA.View, bufferB.View, maskBuffer.View); + + accelerator.Synchronize(); + + mask = maskBuffer.GetAsArray2D(); + } + + public void EdgeDetect(Vector3[,] mask, out Vector3[,] edge) { + var dev = gpuContext.GetPreferredDevice(preferCPU:false); + int width = mask.GetLength(0); + int height = mask.GetLength(1); + edge = new Vector3[width, height]; + using Accelerator accelerator = dev.CreateAccelerator(gpuContext); + + using var buffer = accelerator.Allocate2DDenseX(new (width, height)); + buffer.CopyFromCPU(mask); + + var edgeKernel = accelerator.LoadAutoGroupedStreamKernel, uint, uint>(EdgeKernel); + + edgeKernel(new (width, height), buffer.View, (uint)width, (uint)height); + + accelerator.Synchronize(); + + edge = buffer.GetAsArray2D(); + } + + private static string GetInfoString(Accelerator a) + { + StringWriter infoString = new StringWriter(); + a.PrintInformation(infoString); + return infoString.ToString(); + } +} \ No newline at end of file