C# Running computations on GPU

Date: 2025-01-06

Example uses NuGet package ComputeSharp 3.1.0

using System.Diagnostics;
using ComputeSharp;

namespace GpuTestConsole
{
    public class Program
    {
        static void Main(string[] args)
        {
            var sw = new Stopwatch();

            // Input arrays
            float[] input1 = { 1, 2, 3, 4 };
            float[] input2 = { 5, 6, 7, 8 };
            float[] input3 = { 9, 10, 11, 12 };

            // Output array
            float[] output = new float[input1.Length];

            var gpu = GraphicsDevice.GetDefault();
            Console.WriteLine($"GPU: {gpu.Name}");
            Console.WriteLine($"Compute units: {gpu.ComputeUnits}");

            // Allocate buffers once
            using var inputBuffer = gpu.AllocateReadOnlyBuffer<float>(input1.Length);
            using var outputBuffer = gpu.AllocateReadWriteBuffer<float>(output.Length);

            // Create the kernel (without running it yet)
            var kernel = new AddKernel(inputBuffer, inputBuffer, outputBuffer);

            // First computation
            inputBuffer.CopyFrom(input1);
            kernel.InputBuffer2.CopyFrom(input2); // Dynamically set second input buffer

            sw.Start();
            gpu.For(input1.Length, kernel);

            // Copy result
            outputBuffer.CopyTo(output);
            Console.WriteLine($"Result 1: {string.Join(", ", output)}  ({sw.ElapsedMilliseconds} ms)");

            // Second computation with new inputs
            inputBuffer.CopyFrom(input3);
            kernel.InputBuffer2.CopyFrom(input1);

            sw.Restart();
            gpu.For(input1.Length, kernel);

            outputBuffer.CopyTo(output);
            Console.WriteLine($"Result 2: {string.Join(", ", output)}  ({sw.ElapsedMilliseconds} ms)");
        }
    }

    [GeneratedComputeShaderDescriptor]
    [ThreadGroupSize(DefaultThreadGroupSizes.X)]
    public partial struct AddKernel : IComputeShader
    {
        public ReadOnlyBuffer<float> InputBuffer1;
        public ReadOnlyBuffer<float> InputBuffer2;
        public ReadWriteBuffer<float> OutputBuffer;

        public AddKernel(ReadOnlyBuffer<float> input1, ReadOnlyBuffer<float> input2, ReadWriteBuffer<float> output)
        {
            InputBuffer1 = input1;
            InputBuffer2 = input2;
            OutputBuffer = output;
        }

        /*
         Aandachtspunten bij Execute

        Thread Safety:
            De GPU voert de Execute-functie parallel uit voor elk element.
            Vermijd gedeelde variabelen (globals) en zorg dat elke thread alleen zijn eigen index gebruikt.

        Thread Index:
            Gebruik ThreadIds.X voor de 1D-index. Voor 2D- of 3D-berekeningen kun je ook ThreadIds.Y en ThreadIds.Z gebruiken.
            Controleer altijd of de thread binnen het bereik van de buffer zit om out-of-bounds fouten te voorkomen.

        Buffer Types:
            Gebruik ReadOnlyBuffer<T> voor buffers die niet worden gewijzigd.
            Gebruik ReadWriteBuffer<T> voor buffers die worden gelezen en geschreven.

        Debugging:
            Fouten in GPU-shaders kunnen lastig te debuggen zijn. Controleer of alle indexberekeningen correct zijn en zorg dat buffers voldoende groot zijn.

        Prestatieoptimalisatie:
            Voorkom onnodige synchronisaties tussen CPU en GPU.
            Combineer meerdere kleine berekeningen in één kernel om overhead te verminderen.
         */

        public void Execute()
        {
            int i = ThreadIds.X;
            OutputBuffer[i] = InputBuffer1[i] + InputBuffer2[i];
        }
    }
}
91780cookie-checkC# Running computations on GPU