---
title: "Compile HLSL with in-memory includes in C#"
url: https://recursiveai.org/oneshader/cookbook/compile-hlsl-with-in-memory-includes/
description: "Compile HLSL strings with in-memory include files in C#. Supply an IncludeResolver, inspect touched files, and handle missing-include diagnostics."
version: "1.7.1"
---

# Compile HLSL with in-memory includes in C#

**OneShader** is a free, open-source HLSL compiler library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies.

[NuGet](https://www.nuget.org/packages/RecursiveAI.OneShader) · [GitHub](https://github.com/recursive-ai-oss/oneshader-csharp) · [Docs](https://recursiveai.org/oneshader/docs/) · [Download example (.zip)](https://recursiveai.org/oneshader/cookbook/compile-hlsl-with-in-memory-includes/compile-hlsl-with-in-memory-includes.zip)

Supply `#include` contents as strings instead of saving shader headers to disk.

```shell
dotnet add package RecursiveAI.OneShader
```

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

```csharp
using OneShader;

var result = ShaderCompiler.Compile(
    "#include \"shared.hlsl\"", "main.hlsl",
    new CompileOptions { IncludeResolver =
        (name, parent, system) =>
            name == "shared.hlsl"
            ? new ResolvedInclude("shared.hlsl",
                "[shader(\"pixel\")] " +
                "float4 P() : SV_Target " +
                "{ return 1; }")
            : null });
Console.WriteLine(result.Success);
foreach (var error in result.Diagnostics)
    Console.WriteLine(error);
```

```text
True
```

Compiles a pixel shader to SPIR-V in memory; it writes no shader files.

Unzip [the example](https://recursiveai.org/oneshader/cookbook/compile-hlsl-with-in-memory-includes/compile-hlsl-with-in-memory-includes.zip), then `dotnet run`.

## The full program

The download’s `Program.cs` supplies the main shader and its shared function as two strings.

```csharp
using OneShader;

const string source = """
#include "shared.hlsl"
[shader("pixel")]
float4 PSMain() : SV_Target { return Shade(); }
""";
const string shared = "float4 Shade() { return float4(1, 0, 0, 1); }";
var result = ShaderCompiler.Compile(source, "main.hlsl", new CompileOptions
{
    IncludeResolver = (requested, includingFile, isSystemInclude) =>
        requested == "shared.hlsl"
            ? new ResolvedInclude("shared.hlsl", shared)
            : null,
});
Console.WriteLine($"Success: {result.Success}");
Console.WriteLine($"Files: {string.Join(", ", result.Files)}");
foreach (var diagnostic in result.Diagnostics)
    Console.WriteLine(diagnostic);

var missing = ShaderCompiler.Compile(source, "main.hlsl", new CompileOptions
{
    IncludeResolver = (requested, includingFile, isSystemInclude) => null,
});
Console.WriteLine($"Missing success: {missing.Success}");
Console.WriteLine($"Missing files: {string.Join(", ", missing.Files)}");
foreach (var diagnostic in missing.Diagnostics)
    Console.WriteLine(diagnostic);
```

```text
Success: True
Files: main.hlsl, shared.hlsl
Missing success: False
Missing files: main.hlsl
main.hlsl(1,10): error OSH0120: cannot find include file 'shared.hlsl'; searched: 'the include resolver'. Check the spelling, or add the directory that contains it to the include paths
main.hlsl(3,38): error OSH0300: The function 'Shade' is not declared. Declare it before this call, or check the spelling.
```

A missing include returns `Success == false` and diagnostics on the include line; the later undeclared-function diagnostic follows because `Shade` was never loaded. With a custom resolver set, fix `OSH0120` by making the resolver return the file, despite the diagnostic’s generic suggestion to add include paths.

## Variations

**Read the compiled entry point.** Add this after the successful compile in the full program. `PSMain` is the function marked `[shader("pixel")]` there.

```csharp
if (result.Success)
    Console.WriteLine(result.Find("PSMain", CompileTarget.SpirV) is not null);
```

```text
True
```

**Handle angle-bracket includes.** Replace `#include "shared.hlsl"` inside the main shader string with `#include <shared.hlsl>`. The same resolver works: it accepts that name in either form. The full program’s success, file list and missing-include diagnostics remain the same.

## Related recipes

- [Compile HLSL from C#](https://recursiveai.org/oneshader/cookbook/compile-hlsl-csharp/)
- [Hot-reload shaders](https://recursiveai.org/oneshader/cookbook/hot-reload-shaders/)
- [Cache shader compiles](https://recursiveai.org/oneshader/cookbook/shader-compile-cache/)

---

## The short answer

Use `ShaderCompiler.Compile(source, path, options)` with `CompileOptions.IncludeResolver`. The callback supplies a `ResolvedInclude` containing the include’s reported path and its text. Return `null` for a name you cannot resolve. The compiler returns diagnostics, reflection and compiled output; you do not need to create a graphics device to compile the shader.

The resolver has three arguments: the requested name, the including file’s path, and a Boolean that distinguishes angle brackets from quotes. These are the inputs your application needs to choose a virtual file. OneShader’s [runtime compiler guide](https://recursiveai.org/oneshader/docs/compiler/) documents compilation from strings and tracking the files read.

## How do I compile HLSL with includes in C#?

Keep the main source and shared headers in memory, then map each requested include name to the correct text. The full program uses an exact name match rather than reading the disk. Its `ResolvedInclude("shared.hlsl", shared)` supplies a logical path, not a command to open a file. That path appears in diagnostics and is passed back as the parent path when the included text requests another include.

For a larger virtual directory tree, resolve relative names against `includingFile` yourself. The callback receives the include name as written; your application decides how that name maps to stored strings. Keep returned paths consistent so errors point to the same virtual file each time. A name alone is sufficient for this flat example, but two folders containing a header with the same name need a parent-aware lookup.

The `[shader("pixel")]` attribute identifies the entry point. A function named `PSMain` is not, by its name alone, an entry point in this API. The program’s shared function is ordinary HLSL; it becomes part of the main source through preprocessing. The compiler’s [API declaration](https://github.com/recursive-ai-oss/oneshader-csharp/blob/0faee10a2ddfa981a7aa4193b4ca11e33a28d4d2/src/OneShader/Api/ShaderCompiler.cs#L25-L36) specifies the complete resolver signature.

`result.Files` lists the main file and includes actually read. In the successful example it contains `main.hlsl` and `shared.hlsl`. This is useful when an editor or asset system needs to know which stored texts contributed to a result. It is not a list of attempted include names: when the resolver returns `null`, the missing header is not added as a successfully read file.

## Why is my HLSL include file not found?

With a custom resolver, the lookup succeeds only when that callback returns a `ResolvedInclude`. In the failure run above, the callback deliberately returns `null` for every request. Adding a disk include directory does not repair a resolver that refuses the requested name: the custom resolver is the lookup path for that compile.

Check the spelling and your virtual path mapping first. Then check which parent file requested the header and whether your callback distinguishes quotes from angle brackets. The example deliberately accepts both forms. Microsoft’s [include directive reference](https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-appendix-pre-include) describes the HLSL syntax; lookup policy in this example belongs to the supplied callback.

Treat `Success` as the decision to accept a new compiled shader. Print all diagnostics, but fix the first missing include before chasing errors caused by the absent declarations. The full program demonstrates that distinction: `OSH0120` is the failed lookup, while `OSH0300` reports the function that the missing header would have declared.
