---
title: "Run a compute shader in C#"
url: https://recursiveai.org/onegpu/cookbook/run-a-compute-shader-in-csharp/
description: "Compile HLSL at build time, bind a typed buffer, dispatch a Vulkan compute shader and read its output in C# with OneGpu. Includes a runnable example."
version: "1.0.0"
---

# Run a compute shader in C#

**OneGpu** is a free, open-source GPU compute and rendering library for .NET from Recursive AI, on Vulkan, Metal and WebGPU. MIT or Apache-2.0, no bundled native binaries.

[NuGet](https://www.nuget.org/packages/RecursiveAI.OneGpu.Backends) · [GitHub](https://github.com/recursive-ai-oss/onegpu-csharp) · [Docs](https://recursiveai.org/onegpu/docs/) · [Download example (.zip)](https://recursiveai.org/onegpu/cookbook/run-a-compute-shader-in-csharp/run-a-compute-shader-in-csharp.zip)

A compute shader runs calculations on the GPU without drawing a frame. This example runs HLSL on Vulkan; it needs a Vulkan 1.3 driver on Windows or Linux.

On Apple silicon Macs, OneGpu uses Metal; this SPIR-V-only example targets Vulkan (see [platform support](https://recursiveai.org/onegpu/docs/overview/)).

```shell
dotnet new console
dotnet add package RecursiveAI.OneGpu.Backends
dotnet add package RecursiveAI.OneShader.Build
```

Requires [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0) (net10.0); .NET 9 and earlier aren’t supported.

`Fill.Main` is generated from `Fill.hlsl` by OneShader.Build; both the shader and project settings are shown below and included in the zip.

```csharp
using RecursiveAI.OneGpu;
using Example.Shaders;

using var device = GpuBackends.CreateDevice();
using var output = device.CreateBuffer<uint>(4);
using var kernel = device.CreateComputeKernel(
    new ShaderSource(ShaderStage.Compute, Fill.Main.EntryPoint)
    { SpirV = Fill.Main.SpirV, Layout = Fill.Main.Layout });
using var group = kernel.CreateBindGroup(device, 0,
    [BindGroupEntry.ForBuffer(0, output.Buffer)]);
var commands = device.BeginCommands();
var pass = commands.BeginComputePass();
pass.SetPipeline(kernel.Pipeline);
pass.SetBindGroup(0, group);
pass.Dispatch(1, 1, 1);
pass.EndPass();
device.Submit(commands);
var values = new uint[4];
await output.ReadAsync(values);
Console.WriteLine(string.Join(", ", values));
```

Writes four GPU-generated integers and prints:

```text
0, 10, 20, 30
```

Save this as `Fill.hlsl` beside `Program.cs`:

```hlsl
[[vk::binding(0, 0)]] RWStructuredBuffer<uint> Result;
[shader("compute")]
[numthreads(4, 1, 1)]
void Main(uint3 id : SV_DispatchThreadID)
{
    Result[id.x] = id.x * 10;
}
```

The project pins the released packages and the generated namespace:

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <OneShaderNamespace>Example.Shaders</OneShaderNamespace>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="RecursiveAI.OneGpu.Backends" Version="1.0.0" />
    <PackageReference Include="RecursiveAI.OneShader.Build" Version="1.7.0" PrivateAssets="all" />
  </ItemGroup>
</Project>
```

If the shader requires an unsupported device capability, `CreateComputeKernel` throws `GpuUnsupportedException` naming it before creating the pipeline.

Unzip [the example](https://recursiveai.org/onegpu/cookbook/run-a-compute-shader-in-csharp/run-a-compute-shader-in-csharp.zip), then `dotnet run`.

## Variations

Replace the fixed dispatch with a group count calculated from the reflected workgroup size:

```csharp
var (x, y, z) = kernel.GroupsFor(4);
pass.Dispatch(x, y, z);
```

```text
0, 10, 20, 30
```

## Related recipes

- [Read a GPU buffer back to the CPU in C#](https://recursiveai.org/onegpu/cookbook/read-back-a-gpu-buffer/)
- [Choose a Vulkan physical device in C#](https://recursiveai.org/onegpu/cookbook/pick-a-vulkan-device/)
- [GPU matrix multiplication in C#](https://recursiveai.org/onegpu/cookbook/multiply-matrices-on-the-gpu/)

## The short answer

Run an HLSL compute entry point through OneGpu’s `CreateComputeKernel`, bind its storage buffers, record a compute pass and submit it. `ReadAsync` then copies the result into managed memory. The GPU executes the shader; C# creates resources and records commands. No window or render surface is needed for this calculation.

The example uses OneGpu 1.0.0 and OneShader.Build 1.7.0. The build package compiles `Fill.hlsl` into SPIR-V and generates the `Fill.Main` C# type. It is not a handwritten wrapper, and installing only the runtime package does not generate it. Keep the HLSL file in the project and the namespace setting shown above when copying this example.

## How to run a compute shader?

There are two different steps: compile the shader when building the project, then dispatch it when running the program. OneShader.Build supplies the compiled entry point and its reflection. The reflection describes the shader’s resource bindings and workgroup size. OneGpu uses it to build the pipeline layout instead of asking the application to duplicate that layout.

In this shader, `[[vk::binding(0, 0)]]` declares a writable storage buffer at binding zero in group zero. The C# bind group connects that slot to `output.Buffer`. Both sides use unsigned 32-bit integers, so each C# `uint` corresponds to one HLSL `uint`. Matching the element layout is the application’s responsibility; changing only one side to an unrelated struct does not convert the data.

A workgroup is a group of shader invocations. `[numthreads(4, 1, 1)]` declares four invocations per workgroup. `Dispatch(1, 1, 1)` launches one workgroup, so the shader writes indices zero through three. This particular shader has no bounds check and must not be dispatched over more than those four elements. When adapting it to variable-sized arrays, add an element-count parameter and guard accesses in the shader before rounding up the dispatch size.

`EndPass` finishes recording the pass, not executing it. `Submit` sends the recorded work to the device. The awaited readback is where the CPU result becomes available. Reading the newly allocated C# array before that await completes is not a way to inspect GPU progress.

## What does a compute shader do?

It executes an entry point over a dispatch grid rather than producing rasterized triangles. Here each invocation computes its own array index multiplied by ten. The HLSL file does the arithmetic; the C# program prints the returned values. A compute shader can therefore produce data without producing an image.

The [compute surface documentation](https://recursiveai.org/onegpu/docs/compute/) describes the capability checks applied when a kernel is created. Workgroup dimensions, required scalar types and subgroup operations are checked against the chosen device. Unsupported requirements throw `GpuUnsupportedException` with `Capability`, `Expected` and `Actual`; selecting a discrete adapter alone does not establish that every shader feature is available.

This example passes SPIR-V only and is intentionally a Vulkan program. OneGpu also exposes other backends, but changing the selected backend is not enough to make a SPIR-V-only `ShaderSource` work there. Supply the corresponding generated shader representation when targeting another backend. Dispose the kernel, bind group and buffers when finished; the `using` declarations in the example keep their lifetimes inside the device’s lifetime.
