---
title: "Compile HLSL in a C# project with dotnet build"
url: https://recursiveai.org/oneshader/cookbook/compile-hlsl-csharp/
description: "Compile HLSL shaders as part of dotnet build in a C# project, with shader errors reported as build errors on the HLSL line, shared include files, defines…"
version: "1.2.0"
---

# Compile HLSL in a C# project with dotnet build

Compile HLSL shaders as part of `dotnet build` in a C# project, with shader errors reported as build errors on the HLSL line, shared include files, defines, a custom namespace and per-file targets.

Reference `RecursiveAI.OneShader.Build` and every `*.hlsl` file in the project is compiled on build into SPIR-V embedded in the assembly, next to generated C# for its constant buffers, bindings and entry points. This recipe shows the project-level settings. [Getting started](https://recursiveai.org/oneshader/docs/getting-started/) shows the generated code itself.

## The project

```xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <OneShaderNamespace>Game.Rendering.Shaders</OneShaderNamespace>
    <OneShaderIncludeDirectories>Shaders/Common</OneShaderIncludeDirectories>
    <OneShaderDefines>RADIUS=4</OneShaderDefines>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="RecursiveAI.OneShader.Build" Version="1.2.0" />
  </ItemGroup>

  <ItemGroup>
    <!-- Every *.hlsl is compiled. Listing one gives it metadata: this one also ships to the web build. -->
    <OneShader Include="Shaders/Present.hlsl" Targets="SpirV;Wgsl" />
  </ItemGroup>

</Project>
```

`Shaders/Common/Blur.hlsli`, shared by two variants:

```hlsl
Texture2D<float4> Source : register(t0);
SamplerState Linear : register(s1);
cbuffer Blur : register(b2) { float2 TexelSize; };

[shader("pixel")]
float4 PSMain(float2 uv : TEXCOORD0) : SV_Target
{
    float2 step = HORIZONTAL ? float2(TexelSize.x, 0) : float2(0, TexelSize.y);
    float4 sum = 0;
    [unroll] for (int i = -RADIUS; i <= RADIUS; i++)
        sum += Source.Sample(Linear, uv + step * i);
    return sum / (2 * RADIUS + 1);
}
```

`Shaders/BlurH.hlsl`:

```hlsl
#define HORIZONTAL 1
#include "Blur.hlsli"
```

`Shaders/BlurV.hlsl`:

```hlsl
#define HORIZONTAL 0
#include "Blur.hlsli"
```

`Shaders/Present.hlsl`, a full-screen triangle:

```hlsl
Texture2D<float4> Frame : register(t0);
SamplerState Point : register(s1);

struct VSOut { float4 Position : SV_Position; float2 Uv : TEXCOORD0; };

[shader("vertex")]
VSOut VSMain(uint id : SV_VertexID)
{
    VSOut o;
    o.Uv = float2((id << 1) & 2, id & 2);
    o.Position = float4(o.Uv * float2(2, -2) + float2(-1, 1), 0, 1);
    return o;
}

[shader("pixel")]
float4 PSMain(VSOut i) : SV_Target { return Frame.Sample(Point, i.Uv); }
```

`Program.cs`:

```csharp
using Game.Rendering.Shaders;

Console.WriteLine($"BlurH.PSMain: {BlurH.PSMain.SpirV.Length} bytes of SPIR-V");
Console.WriteLine($"BlurV.PSMain: {BlurV.PSMain.SpirV.Length} bytes of SPIR-V");
Console.WriteLine($"Blur constant buffer: {BlurH.Blur.Size} bytes at binding {BlurH.Bindings.Blur.Binding}");
Console.WriteLine($"Present.VSMain: {Present.VSMain.SpirV.Length} bytes of SPIR-V, {Present.VSMain.Wgsl.Length} bytes of WGSL");
```

```text
BlurH.PSMain: 1580 bytes of SPIR-V
BlurV.PSMain: 1580 bytes of SPIR-V
Blur constant buffer: 16 bytes at binding 2
Present.VSMain: 836 bytes of SPIR-V, 740 bytes of WGSL
```

## An error in an included file

Misspell `step` in `Blur.hlsli` and `dotnet build` stops on the include’s own line:

```text
/tmp/oneshader-docs/cookbook/Build/Shaders/Common/Blur.hlsli(11,43): error OSH0300: 'stepp' is not declared. Did you mean 'step'? [/tmp/oneshader-docs/cookbook/Build/Build.csproj]
```

## Good to know

- **Default items.** Every `*.hlsl` in the project is a `OneShader` item. `.hlsli` files aren’t, so they’re only compiled where they’re included. To give one file metadata, list it with `<OneShader Include="...">`, as above: since 1.2.0 the default glob skips files that are already listed. `<OneShader Update="...">` doesn’t work, because the package adds the default items after your project file. Setting `EnableDefaultOneShaderItems` to `false` turns the glob off.
- **One class per file.** The class is named after the file in PascalCase, in `$(OneShaderNamespace)` (default `<RootNamespace>.Shaders`). Two `OneShader` items for the same file with different `ClassName` and `Defines` give only one class, without a warning. For variants of one source, use `Axes` or `OneShaderVariant` items ([Shader variants with Axes](https://recursiveai.org/oneshader/cookbook/shader-variants-axes/)), or give each variant its own small `.hlsl` file, as above.
- **Item metadata**, all optional: `EntryPoints` (`VSMain:Vertex;PSMain:Pixel`) (instead of the `[shader]` attributes), `Defines="A;B=2"`, `IncludeDirectories`, `Namespace`, `ClassName`, `Targets`, `Enable16BitTypes`, `Axes`, `MaxVariants`. **Project properties:** `OneShaderNamespace`, `OneShaderTargets` (default `SpirV`), `OneShaderDefines`, `OneShaderIncludeDirectories`, `OneShaderEnable16BitTypes`, `OneShaderMaxVariants`.
- **Includes** are searched in the including file’s folder first, then in the include directories.
- **Name clashes are errors.** A `cbuffer` named like its file (`cbuffer Lighting` in `Lighting.hlsl`) is error OSH0951, because the struct and the class would share a name.
- **Incremental.** A shader is compiled again only when it, a file it includes, the options or the compiler change. The output is deterministic. `dotnet build -v:d` logs the count; after the typo above was fixed it was `OneShader: 2 compiled, 1 up to date.` (the two blur files share the include).
- **The build package doesn’t flow** to projects that reference yours: only projects with shaders reference it. The support types live in `RecursiveAI.OneShader`, so a rendering layer in another assembly can take them.
- **`dotnet build` only.** Visual Studio’s .NET Framework MSBuild can’t load the task.

Every generated member is listed in [Generated bindings](https://recursiveai.org/oneshader/docs/generated-bindings/). For variants compiled at run time instead, see [Shader variants with defines and includes](https://recursiveai.org/oneshader/cookbook/shader-variants-defines-includes/).

The options compared, dxc, Vortice.Dxc and OneShader: [Compile HLSL shaders in C#](https://recursiveai.org/oneshader/articles/csharp-shader-compiler/).
