ComputeSharp icon indicating copy to clipboard operation
ComputeSharp copied to clipboard

Error when using multiple threads

Open viruseg opened this issue 1 year ago • 0 comments

ComputeSharp 3.0.2 .net 9.0.203

If you run the shader execution from two threads at the same time, then one of the threads will hang forever without any errors.

If you run three threads, the process will fail:

Unhandled exception. System.ArgumentException: An item with the same key has already been added.
   at System.Runtime.CompilerServices.ConditionalWeakTable`2.Add(TKey key, TValue value)
   at ComputeSharp.Shaders.Loading.PipelineDataLoader`1.CreatePipelineData(GraphicsDevice device) in /_/src/ComputeSharp/Shaders/Loading/PipelineDataLoader{T}.cs:line 53
   at System.Runtime.CompilerServices.ConditionalWeakTable`2.GetValueLocked(TKey key, CreateValueCallback createValueCallback)
   at System.Runtime.CompilerServices.ConditionalWeakTable`2.GetValue(TKey key, CreateValueCallback createValueCallback)
   at ComputeSharp.Shaders.Loading.PipelineDataLoader`1.GetPipelineData(GraphicsDevice device) in /_/src/ComputeSharp/Shaders/Loading/PipelineDataLoader{T}.cs:line 31
   at ComputeSharp.ComputeContext.Run[T](Int32 x, Int32 y, Int32 z, T& shader) in /_/src/ComputeSharp/Shaders/ComputeContext.cs:line 166
   at ComputeSharp.ComputeContext.Run[T](Int32 x, Int32 y, T& shader) in /_/src/ComputeSharp/Shaders/ComputeContext.cs:line 139
   at ComputeSharp.ComputeContextExtensions.For[T](ComputeContext& context, Int32 x, Int32 y, T& shader) in /_/src/ComputeSharp/Shaders/Extensions/ComputeContextExtensions.cs:line 503
   at Program.<<Main>$>g__Execute|0_3(Byte[] sourceBytes, Rectangle cropRect, ImageFormat targetImageFormat, GraphicsDevice graphicsDevice) in C:\ConsoleApp\Program.cs:line 40
   at Program.<>c__DisplayClass0_0.<<Main>$>b__0() in C:\ConsoleApp\Program.cs:line 12
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location ---
   at Program.<Main>$(String[] args) in C:\ConsoleApp\Program.cs:line 28
   at Program.<Main>(String[] args)

Here is a sample code to reproduce the error:

using System.Drawing;
using ComputeSharp;

#pragma warning disable CA1416

var graphicsDevice = GraphicsDevice.GetDefault();
var fileBytes = File.ReadAllBytes("0.png");
var rectangle = new Rectangle(100, 100, 100, 100);

var task0 = Task.Run(() =>
{
    var bytes = Execute(fileBytes, rectangle, ImageFormat.Bmp, graphicsDevice);
    Console.WriteLine($"0 {bytes.Length}");
});

var task1 = Task.Run(() =>
{
    var bytes = Execute(fileBytes, rectangle, ImageFormat.Bmp, graphicsDevice);
    Console.WriteLine($"1 {bytes.Length}");
});

var task2 = Task.Run(() =>
{
    var bytes = Execute(fileBytes, rectangle, ImageFormat.Bmp, graphicsDevice);
    Console.WriteLine($"1 {bytes.Length}");
});

await Task.WhenAll(task0, task1, task2);

Console.WriteLine("Complete");
return;

byte[] Execute(byte[] sourceBytes, Rectangle cropRect, ImageFormat targetImageFormat,  GraphicsDevice graphicsDevice)
{
    var sourceTex = graphicsDevice.LoadReadOnlyTexture2D<Bgra32, float4>(sourceBytes);
    var targetTex = graphicsDevice.AllocateReadWriteTexture2D<Bgra32, float4>(cropRect.Width, cropRect.Height);

    using (var context = graphicsDevice.CreateComputeContext())
    {
        context.For(targetTex.Width,
                    targetTex.Height,
                    new InternalCropShader(sourceTex,
                                           targetTex,
                                           cropRect.X,
                                           cropRect.Y));
    }

    var ms = new MemoryStream();
    targetTex.Save(ms, targetImageFormat);
    return ms.ToArray();
}

[ThreadGroupSize(DefaultThreadGroupSizes.XY)]
[GeneratedComputeShaderDescriptor]
internal readonly partial struct InternalCropShader
(
    IReadOnlyNormalizedTexture2D<float4> sourceTex,
    IReadWriteNormalizedTexture2D<float4> targetTex,
    int offsetX,
    int offsetY
) : IComputeShader
{
    public void Execute()
    {
        var sourceXY = ThreadIds.XY + new int2(offsetX, offsetY);
        targetTex[ThreadIds.XY] = sourceTex[sourceXY];
    }
}

viruseg avatar Apr 14 '25 08:04 viruseg