Recursive AI

OneShader

All oneshader docs

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 CI .NET 10 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:

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.

Full results, with allocations per compile, output size, the losses and the method →

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). 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, WGSL).
  • 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); 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). 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).

Install

For building shaders into your project:

Terminal
dotnet add package RecursiveAI.OneShader.Build

For the compiler itself, for hot reload and tools:

Terminal
dotnet add package RecursiveAI.OneShader

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

Terminal
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, RecursiveAI.OneShader and RecursiveAI.OneShader.Tool.

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:

C#
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):

C#
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; the changelog lists what each release changes.

Where to go

Building

Terminal
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 or Apache-2.0 at your option.

Copyright (c) Recursive AI, LLC.