Generate typed C# buffer structs and binding constants from HLSL during your .NET build.
dotnet add package RecursiveAI.OneShader.BuildRequires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.
Fill is a generated type, not a class you write. The snippet requires the shader and project configuration shown below.
using Example.Shaders; // Fill: generated from Shaders/Fill.hlsl. Console.WriteLine(Fill.Settings.Size); Console.WriteLine(Fill.Bindings.Output.Binding);
16 1
Unzip the example, then dotnet run.
Fill is produced by this configuration in project.csproj (the download pins version 1.7.1):
<PropertyGroup> <RootNamespace>Example</RootNamespace> </PropertyGroup> <ItemGroup> <PackageReference Include="RecursiveAI.OneShader.Build" Version="1.7.1" /> <OneShader Include="Shaders/Fill.hlsl" EntryPoints="CSMain:Compute" Namespace="Example.Shaders" ClassName="Fill" /> </ItemGroup>
Shaders/Fill.hlsl, included in the download:
cbuffer Settings : register(b0) { float4 Color; }; RWStructuredBuffer<float4> Output : register(u1); [shader("compute")] [numthreads(8, 1, 1)] void CSMain(uint3 id : SV_DispatchThreadID) { Output[id.x] = Color; }
The full program
The full program constructs the generated constant-buffer struct and prints its size, output binding and compute workgroup dimensions. It compiles the shader; it does not dispatch work on a GPU.
using Example.Shaders; var settings = new Fill.Settings { Color = new(1, 0, 0, 1) }; Console.WriteLine($"Settings: {Fill.Settings.Size} bytes"); Console.WriteLine($"Output: set {Fill.Bindings.Output.Set}, binding {Fill.Bindings.Output.Binding}"); Console.WriteLine($"Workgroup: {Fill.CSMain.ThreadGroupSizeX} x {Fill.CSMain.ThreadGroupSizeY} x {Fill.CSMain.ThreadGroupSizeZ}"); Console.WriteLine($"Red: {settings.Color.X}");
dotnet run prints:
Settings: 16 bytes Output: set 0, binding 1 Workgroup: 8 x 1 x 1 Red: 1
An HLSL compile error appears as an MSBuild diagnostic at the shader’s file and line; fix that error before consuming the generated types.
Variations
Read the constant-buffer binding instead. In the same shader project, this prints 0.
using Example.Shaders; Console.WriteLine(Fill.Bindings.Settings.Binding);
Related recipes
The short answer
Reference RecursiveAI.OneShader.Build, include an HLSL shader in the project, and build with the .NET CLI. Use the generated C# structs and binding constants instead of manually duplicating shader layouts.
How do I generate C# shader bindings?
Add RecursiveAI.OneShader.Build to the project that contains your HLSL files. Its default item glob includes every *.hlsl file in that project. During dotnet build, the package compiles the shaders and generates a static partial C# class per file. Fill.hlsl in this example becomes Example.Shaders.Fill because the project specifies the Example root namespace.
The generated class exposes nested constant-buffer types, resource-binding constants and entry-point classes. You do not declare Fill.Settings or copy its members into a hand-written duplicate. Reference the generated type directly, as the full program does. This makes the compiled shader’s layout the source of the C# field offsets rather than relying on a separate packing calculation.
The build package brings the support types from RecursiveAI.OneShader with it. Use the .NET CLI’s build, not Visual Studio’s .NET Framework MSBuild: the build task requires .NET 10 (dotnet build). Only the projects containing shaders need the build package; it does not flow to consumers. See generated bindings for the full generated surface.
How do I keep shader code and C# in sync?
Keep the HLSL file in the same project as the generated binding consumer. Editing the shader causes the build to regenerate the binding information. Includes, compiler changes and changed compilation options also participate in recompilation. A renamed buffer or entry point then changes the generated C# API, allowing a consumer that still uses the old name to fail at compilation instead of carrying an obsolete layout silently.
For each constant buffer the generator emits a blittable, explicitly laid-out struct. Its Size constant and field offsets match the compiled shader. Float vectors and matrices use System.Numerics types; this shader’s float4 Color becomes a field that accepts a four-component vector. The example’s 16-byte size comes from this shader, not a fixed size for all constant buffers.
Resource bindings expose set, binding, count and kind, plus a span describing the resources and stage use. The sample’s register(u1) is reflected as output binding 1. Consume the generated values when creating your graphics API resources; the console program only prints them. Binding generation does not allocate a GPU buffer, upload the settings or execute a compute dispatch.
How do I configure shader entry points in a C# project?
The build finds entry points from HLSL shader attributes for vertex, pixel and compute functions. Here [shader("compute")] marks CSMain, and numthreads supplies its workgroup dimensions. Those dimensions are available as generated constants, so the host can read them without duplicating the values from HLSL.
You can instead provide explicit entry points with OneShader item metadata. The configuration above names the compute entry point, output namespace and class. Listing a file explicitly does not compile it twice: the default glob skips files already listed. The same metadata mechanism supports defines, include directories, target backends and accessibility.
Generated classes are public by default. Set accessibility to internal when bindings are an implementation detail rather than an API your consumers should reference. The default target embeds SPIR-V; additional targets expose their own generated output properties. Accessing generated shader bytes is separate from consuming binding constants: this sample needs no device and does not claim to test GPU execution.