Yotsuba Shader
English

C# SHADERS

Your shader, written in C#.

Use classes, structs, and methods to describe your shader. Attributes identify the stages, the values passed between them, and the resources supplied by your application.

This is a pre-release authoring API. The examples below use declarations present in the repository; complete shader compilation is still in development. HLSL is the default syntax throughout this guide.

01

Stages

A method declares vertex, fragment, or compute work.

02

Inputs and outputs

Structs describe the values received and returned by a stage.

03

Resources

Buffers, textures, and samplers carry data provided by your application.

04

Your types

Type annotations describe how compatible C# data maps to shader types.

One class. Two connected stages.

The vertex method receives a position and color. It writes a clip-space position and passes the color to the fragment method, which returns the interpolated color.

Both methods share VS_Output. You do not need a second struct just to receive the same data in the fragment stage.

The application supplies vertex data, configures the graphics pipeline, and issues draw calls. These attributes describe the shader; they do not perform those application tasks.

This example uses MonoGame types for familiarity. Their names alone do not make them native GPU vectors; native mapping requires compatible type metadata.

BasicColorShader.cs
using Microsoft.Xna.Framework;
using YotsubaShader.Language;
using YotsubaShader.HlslSyntax;

public class BasicColorShader
{
    public struct VS_Input
    {
        [POSITION]
        public Vector2 Position;

        [COLOR]
        public Vector4 Color;
    }

    public struct VS_Output
    {
        [SV_Position]
        public Vector4 Position;

        [COLOR]
        public Vector4 Color;
    }

    [ShaderStage(Stage.Vertex)]
    public VS_Output VS(VS_Input input)
    {
        VS_Output output = new VS_Output();

        output.Position = new Vector4(
            input.Position.X,
            input.Position.Y,
            0.0f,
            1.0f
        );

        output.Color = input.Color;

        return output;
    }

    [ShaderStage(Stage.Fragment)]
    [return: SV_Target(0)]
    public Vector4 FS(VS_Output input)
    {
        return input.Color;
    }
}

What each attribute says

AttributePurpose
[ShaderStage(Stage.Vertex)]Marks the vertex entry point.
[ShaderStage(Stage.Fragment)]Marks the fragment entry point.
[POSITION]Identifies a vertex position supplied by the application.
[COLOR]Identifies color data, including the value passed between stages.
[SV_Position]Declares the vertex clip-space position; as a fragment input it describes the rasterized position.
[return: SV_Target(0)]Routes the fragment return value to color target 0.

For additional vertex data, use HLSL semantics such as [TEXCOORD(0)] or [NORMAL]. The separate SPIR-V vocabulary also offers numbered locations; you do not need to mix it into an HLSL example.

Declare what your application provides.

Use cbuffer for uniform data and register to identify resource slots. The register prefix distinguishes constant buffers (b), textures (t), samplers (s), and writable resources (u).

These are resource declarations. Your engine creates and binds the actual GPU resources. The current metadata types do not provide a texture sampling method or a complete runtime binding system.

Read about resources and bindings →
SurfaceResources.cs
using YotsubaShader.HlslSyntax.Resources;
using YotsubaShader.HlslSyntax.Resources.Types;

public struct MaterialData
{
    public float Exposure;
    public float Roughness;
}

public class SurfaceResources
{
    [cbuffer, register(RegisterPrefix.b, 0)]
    public MaterialData Material;

    [register(RegisterPrefix.t, 0)]
    public Texture2D Albedo;

    [register(RegisterPrefix.s, 0)]
    public SamplerState Sampler;
}

Keep compatible types from your project.

A C# struct can describe a native shader vector when its type and components carry the corresponding annotations. This makes the mapping explicit without relying on the type name.

Annotate types you own. Importing a third-party namespace does not add metadata to its types. You can share declarations as NuGet libraries, while GPU compilation of arbitrary library methods remains unfinished.

Learn how to map your types →
Float2.cs
using YotsubaShader.Language.Native.Types.Vector;

[Vector]
public struct Float2
{
    [VectorComponent(0)] public float X;
    [VectorComponent(1)] public float Y;

    public Float2(float x, float y)
    {
        X = x;
        Y = y;
    }
}

Compute is a separate stage.

A compute method uses Stage.Compute. In the HLSL vocabulary, numthreads(8, 8, 1) declares the local workgroup dimensions. It does not use a vertex-to-fragment connection.

The example declares an empty compute entry point. Executing useful GPU work requires supported body operations, compiled output, resource bindings, and a dispatch from your application.

ComputeShader.cs
using YotsubaShader.Language;
using YotsubaShader.HlslSyntax;

public class ComputeShader
{
    [ShaderStage(Stage.Compute)]
    [numthreads(8, 8, 1)]
    public void CS()
    {
    }
}

Start with HLSL. Explore other vocabularies when you need them.

The syntax controls the attribute names you write. It does not select the output graphics API. WGSL-inspired authoring attributes already exist; generating WGSL output is planned for 2027.

See how the shader is compiled →