Skip to content

LostBeard/BlazorWASMWebGPUComputeDemo

Repository files navigation

BlazorWASMWebGPUComputeDemo

This project contains Blazor WebAssembly WebGPU demos using SpawnDev.BlazorJS.

Compute Boids

Compute Boids Image

Run computations on the GPU

using var adapter = JS.IsUndefined("navigator.gpu?.requestAdapter") ? null : await JS.CallAsync<GPUAdapter>("navigator.gpu.requestAdapter");
if (adapter == null)
{
    Log("WebGPU not supported");
    return;
}
using var device = await adapter.RequestDevice();
if (device == null)
{
    Log("WebGPU not supported");
    return;
}
Log("WebGPU supported");

using var module = device.CreateShaderModule(new GPUShaderModuleDescriptor
{
    Label = "doubling compute module",
    Code = @"
    @group(0) @binding(0) var<storage, read_write> data: array<f32>;

    @compute @workgroup_size(1) fn computeSomething(@builtin(global_invocation_id) id: vec3u) {
        let i = id.x;
        data[i] = data[i] * 2.0;
    }
    ",
});

using var pipeline = device.CreateComputePipeline(new GPUComputePipelineDescriptor
{
    Label = "doubling compute pipeline",
    Layout = GPUAutoLayoutMode.Auto,
    Compute = new GPUProgrammableStage
    {
        Module = module,
    }
});

// input data
var input = new float[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Log($@"Input: {string.Join(", ", input)}");

using var inputFloatArray = new Float32Array(input);
var inputByteLength = inputFloatArray.ByteLength;

// create a buffer on the GPU to hold our computation
// input and output
using var workBuffer = device.CreateBuffer(new GPUBufferDescriptor
{
    Label = "work buffer",
    Size = (ulong)inputByteLength,
    Usage = GPUBufferUsage.Storage | GPUBufferUsage.CopySrc | GPUBufferUsage.CopyDst,
});

// Copy our input data to that buffer
device.Queue.WriteBuffer(workBuffer, 0, inputFloatArray);

// Create a buffer for the result; in this case it is the same size as the input
using var resultBuffer = device.CreateBuffer(new GPUBufferDescriptor
{
    Label = "result buffer",
    Size = (ulong)inputByteLength,
    Usage = GPUBufferUsage.MapRead | GPUBufferUsage.CopyDst
});

// Create bind group
using var bindGroup = device.CreateBindGroup(new GPUBindGroupDescriptor
{
    Label = "bindGroup for work buffer",
    Layout = pipeline.GetBindGroupLayout(0),
    Entries = new GPUBindGroupEntry[] {
        new GPUBindGroupEntry
        {
            Binding = 0,
            Resource = new GPUBufferBinding{ Buffer = workBuffer }
        }
    },
});

// Encode commands to do the computation
using var encoder = device.CreateCommandEncoder(new GPUCommandEncoderDescriptor
{
    Label = "doubling encoder",
});

// Begin pass
using var pass = encoder.BeginComputePass(new GPUComputePassDescriptor
{
    Label = "doubling compute pass",
});
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup);
pass.DispatchWorkgroups((uint)inputByteLength);
pass.End();

// Encode a command to copy the results to a mappable buffer.
encoder.CopyBufferToBuffer(workBuffer, 0, resultBuffer, 0, resultBuffer.Size);

// Finish encoding and submit the commands
using var commandBuffer = encoder.Finish();
device.Queue.Submit([commandBuffer]);

// Read the results
await resultBuffer.MapAsync(GPUMapMode.Read);
using var outputFloatArray = new Float32Array(resultBuffer.GetMappedRange());

float[] output = outputFloatArray.ToArray();
Log($@"Result: {string.Join(", ", output)}");

resultBuffer.Unmap();