---
title: "OneShader"
url: https://recursiveai.org/oneshader/docs/overview/
description: "What OneShader is and why: shader errors as build errors, C# that matches the shader, three targets from one front end, checked against DXC and real GPUs, and its speed against DXC with the losses shown."
version: "1.2.0"
source: https://github.com/recursive-ai-oss/oneshader-csharp/blob/8896e63be3a4f246295427451f15e4fcfb5535cf/README.md
---

# OneShader

**A HLSL compiler for .NET, written in C# with no dependency beyond .NET itself.**
Modern HLSL (Shader Model 6 style) for vertex, pixel and compute shaders goes in;
SPIR-V for Vulkan, Metal Shading Language and WGSL come out, each with
reflection. An MSBuild task and a source generator compile your `*.hlsl` files
during `dotnet build`, report errors on the HLSL line, and generate C# structs
and binding code that match the compiled shader.

[![NuGet](https://img.shields.io/nuget/v/RecursiveAI.OneShader)](https://www.nuget.org/packages/RecursiveAI.OneShader)
[![CI](https://github.com/recursive-ai-oss/oneshader-csharp/actions/workflows/ci.yml/badge.svg)](https://github.com/recursive-ai-oss/oneshader-csharp/actions/workflows/ci.yml)
[![.NET 10](https://img.shields.io/badge/.NET-10.0-512BD4)](#install)
[![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue)](#license)

How many times faster or slower OneShader compiles a shader than DXC (the
released binary, SPIR-V output), one process per entry point over 68 corpus
entry points, geometric mean, log scale. A bar to the right is a win for
OneShader, a bar to the left is a loss:

<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/recursive-ai-oss/oneshader-csharp/main/docs/img/oneshader-hero-dark.svg">
  <img alt="Bar chart: how many times faster or slower OneShader compiles than DXC, log scale. The NativeAOT build is about 2.4 times faster when warm and 2.7 times faster on the first run. Started as an ordinary .NET process, it is slower: about 5 to 6 times with ReadyToRun and 13 to 14 times with the JIT." src="https://raw.githubusercontent.com/recursive-ai-oss/oneshader-csharp/main/docs/img/oneshader-hero-light.svg">
</picture>

[Full results, with allocations per compile, output size, the losses and the method →](https://recursiveai.org/oneshader/docs/performance/)

## Why OneShader

- **Shader errors are build errors.** Put `*.hlsl` in a project and reference
  `RecursiveAI.OneShader.Build`: a mistake shows up as
  `Shaders/Sprite.hlsl(24,45): error OSH0300: 'Atlass' is not declared. Did you mean 'Atlas'?`
  on the HLSL line, with a stable code, not as a black screen at run time.
- **C# that matches the shader.** Constant buffers, vertex inputs and resource
  bindings become blittable structs and constants whose offsets equal the
  compiled shader's, so a CPU/GPU layout mismatch fails the build
  ([generated bindings](https://recursiveai.org/oneshader/docs/generated-bindings/)). One layout serves every
  target.
- **Three targets from one front end.** SPIR-V (Vulkan 1.2+) is the default;
  Metal Shading Language and WGSL are opt-in per project
  (`<OneShaderTargets>SpirV;Msl;Wgsl</OneShaderTargets>`). A feature a target
  cannot express is a diagnostic naming the target, never a silent
  miscompile ([MSL](https://recursiveai.org/oneshader/docs/msl-backend/), [WGSL](https://recursiveai.org/oneshader/docs/wgsl-backend/)).
- **Checked against DXC and real GPUs.** SPIR-V is validated with `spirv-val`
  and every corpus shader is run on the GPU next to the same shader compiled by
  DXC, with the Vulkan validation layers on; WGSL goes through `naga` and a
  browser WebGPU run ([WebGPU parity](https://recursiveai.org/oneshader/docs/webgpu-parity/)); MSL goes through
  Apple's compiler and a Metal run.
- **Safe on text you did not write.** The compiler runs inside your build, so
  bad input yields diagnostics, never an exception or a stack overflow, and
  the front end is fuzzed ([fuzzing](https://github.com/recursive-ai-oss/oneshader-csharp/blob/8896e63be3a4f246295427451f15e4fcfb5535cf/fuzz/README.md)). It contains no
  `unsafe` code, uses no reflection, and is NativeAOT and trim clean.
- **Fast, with the losses shown.** As a NativeAOT binary it compiles the
  corpus about 2.4 times faster than DXC, warm, and every one of the 68 entry
  points is faster. Run as an ordinary .NET process it is slower than DXC on
  every one of them (about 6 times with ReadyToRun, 14 times with the JIT),
  because starting the runtime and the compiler's code dominates a compile that
  takes 0.34 ms at the median once the process is running. Its SPIR-V is 5.7%
  larger than DXC's by default because it keeps debug names, and 8.8% smaller
  with them off. Memory use was not measured
  ([every result](https://recursiveai.org/oneshader/docs/performance/)).

## Install

For building shaders into your project:

```sh
dotnet add package RecursiveAI.OneShader.Build
```

For the compiler itself, for hot reload and tools:

```sh
dotnet add package RecursiveAI.OneShader
```

From a command line or another build system (CMake, Make, scripts), with a DXC-style interface:

```sh
dotnet tool install -g RecursiveAI.OneShader.Tool   # then: oneshader -T ps_6_0 -E PSMain -spirv shader.hlsl -Fo shader.spv
```

Target framework `net10.0`. Build with the .NET SDK (`dotnet build`); Visual
Studio's .NET Framework MSBuild cannot load the build task. Package guides:
[RecursiveAI.OneShader.Build](https://recursiveai.org/oneshader/docs/build-package/),
[RecursiveAI.OneShader](https://recursiveai.org/oneshader/docs/compiler/) and
[RecursiveAI.OneShader.Tool](https://recursiveai.org/oneshader/docs/command-line/).

## Quick start

`Shaders/Sprite.hlsl`:

```hlsl
cbuffer Frame : register(b0) { float4x4 ViewProjection; };
[[vk::binding(1)]] Texture2D<float4> Atlas;
[[vk::binding(2)]] SamplerState AtlasSampler;

struct VSIn { float2 Position : POSITION; float2 Uv : TEXCOORD0; float4 Color : COLOR0; };
struct VSOut { float4 Position : SV_Position; float2 Uv : TEXCOORD0; float4 Color : COLOR0; };

[shader("vertex")]
VSOut VSMain(VSIn i)
{
    VSOut o;
    o.Position = mul(ViewProjection, float4(i.Position, 0, 1));
    o.Uv = i.Uv;
    o.Color = i.Color;
    return o;
}

[shader("pixel")]
float4 PSMain(VSOut i) : SV_Target { return Atlas.Sample(AtlasSampler, i.Uv) * i.Color; }
```

After `dotnet build`, with no reflection and no file I/O at run time:

```csharp
using MyGame.Shaders;   // <RootNamespace>.Shaders

ReadOnlySpan<byte> vs = Sprite.VSMain.SpirV;                 // embedded SPIR-V
var frame = new Sprite.Frame { ViewProjection = camera };    // explicit layout, Size = 64
uint set = Sprite.Bindings.Atlas.Set, binding = Sprite.Bindings.Atlas.Binding;
```

To compile at run time instead (tools, hot reload):

```csharp
using OneShader;

CompileResult result = ShaderCompiler.CompileFile("Shaders/Sprite.hlsl");
if (!result.Success) { foreach (var d in result.Diagnostics) Console.WriteLine(d); return; }
byte[] spirv = result.Find("PSMain", CompileTarget.SpirV)!.ToArray();
```

## Status

**Version 1.2.** Vertex, pixel and compute shaders compile to
optimized SPIR-V, Metal Shading Language and WGSL with reflection; the build
package embeds each selected target next to generated C# bindings (the
`OneShaderTargets` switch), and the runtime API supports hot reload with a
layout-compatibility check. Not supported: geometry, tessellation, mesh and
ray-tracing stages, unbounded resource arrays and the SM 6.5 partitioned wave operations (`WaveMatch`, `WaveMultiPrefix*`), and some
features on some targets (the backend pages list them). The public API and the
generated code follow [Semantic Versioning](https://semver.org/); the
[changelog](https://recursiveai.org/oneshader/docs/changelog/) lists what each release changes.

## Where to go

- **Using it:** the package guides ([build](https://recursiveai.org/oneshader/docs/build-package/),
  [compiler](https://recursiveai.org/oneshader/docs/compiler/)), the
  [generated bindings](https://recursiveai.org/oneshader/docs/generated-bindings/), and the
  [reflection format](https://recursiveai.org/oneshader/docs/reflection-format/).
- **Backends:** [SPIR-V](https://recursiveai.org/oneshader/docs/spirv-backend/), [MSL](https://recursiveai.org/oneshader/docs/msl-backend/),
  [WGSL](https://recursiveai.org/oneshader/docs/wgsl-backend/), the shared [IR](https://recursiveai.org/oneshader/docs/ir/), and
  [browser WebGPU parity](https://recursiveai.org/oneshader/docs/webgpu-parity/).
- **Evidence:** [performance and its losses](https://recursiveai.org/oneshader/docs/performance/) (the
  numbers behind the chart; reproduce with `bench/run.sh`),
  [fuzzing](https://github.com/recursive-ai-oss/oneshader-csharp/blob/8896e63be3a4f246295427451f15e4fcfb5535cf/fuzz/README.md), and the corpus in [`corpus/`](https://github.com/recursive-ai-oss/oneshader-csharp/blob/8896e63be3a4f246295427451f15e4fcfb5535cf/corpus/README.md).
- **What changed:** [CHANGELOG.md](https://recursiveai.org/oneshader/docs/changelog/).

## Building

```sh
dotnet build
dotnet test
```

Build output goes to the directory named by `ONESHADER_ARTIFACTS` (or
`.artifacts` in the repository when it is unset). Tests that compare against
reference compilers use released binaries fetched by `tools/fetch-oracles.sh`;
versions are pinned in `tools/oracles.env`.

## License

Licensed under either of [MIT](https://github.com/recursive-ai-oss/oneshader-csharp/tree/8896e63be3a4f246295427451f15e4fcfb5535cf/LICENSE-MIT) or
[Apache-2.0](https://github.com/recursive-ai-oss/oneshader-csharp/tree/8896e63be3a4f246295427451f15e4fcfb5535cf/LICENSE-APACHE) at your option.

Copyright (c) Recursive AI, LLC.
