Overview#
The five examples below are the same shader: a vertex and a fragment stage, plus a compute stage that writes to a storage buffer. Only the attribute names change. Switch between them with the tabs, or with the Syntax menu at the top of the page; your choice is remembered across the documentation.
One package per syntax#
Each syntax is its own package with a namespace of the same name: YotsubaShader.HlslSyntax, YotsubaShader.GlslSyntax, YotsubaShader.MslSyntax, YotsubaShader.SpirvSyntax and YotsubaShader.WgslSyntax. Resource attributes live in its .Resources namespace, for example YotsubaShader.HlslSyntax.Resources. They reference YotsubaShader.Language for stage and native-type annotations and YotsubaShader.Concepts for shared concepts. Locations and resource attributes are supplied by syntax namespaces. Engines and frameworks build on that core; add only the syntax you write with.
The same shader in five syntaxes#
using YotsubaShader.Language;
using YotsubaShader.HlslSyntax;
using YotsubaShader.HlslSyntax.Resources;
public struct VertexOutput
{
[SV_Position]
public Vector4 Position;
}
public class Triangle
{
[register(RegisterPrefix.u, 0)]
public RWStructuredBuffer<float> Output;
[ShaderStage(Stage.Vertex)]
public VertexOutput Vertex([SV_VertexID] uint id)
{
VertexOutput output = new VertexOutput();
output.Position = new Vector4(0, 0, 0, 1);
return output;
}
[ShaderStage(Stage.Fragment)]
[return: SV_Target(0)]
public Vector4 Fragment(VertexOutput input)
{
return new Vector4(1, 0.4f, 0.7f, 1);
}
[ShaderStage(Stage.Compute)]
[numthreads(8, 8, 1)]
public void Compute([SV_DispatchThreadID] UInt3 id)
{
}
}
using YotsubaShader.Language;
using YotsubaShader.GlslSyntax;
using YotsubaShader.GlslSyntax.Resources;
public struct VertexOutput
{
[gl_Position]
public Vector4 Position;
}
public class Triangle
{
[buffer, layout(0, 0)]
public float[] Output;
[ShaderStage(Stage.Vertex)]
public VertexOutput Vertex([gl_VertexIndex] uint id)
{
VertexOutput output = new VertexOutput();
output.Position = new Vector4(0, 0, 0, 1);
return output;
}
[ShaderStage(Stage.Fragment)]
public Vector4 Fragment(VertexOutput input)
{
return new Vector4(1, 0.4f, 0.7f, 1);
}
[ShaderStage(Stage.Compute)]
[layout_local_size(8, 8, 1)]
public void Compute([gl_GlobalInvocationID] UInt3 id)
{
}
}
using YotsubaShader.Language;
using YotsubaShader.MslSyntax;
using YotsubaShader.MslSyntax.Resources;
public struct VertexOutput
{
[position]
public Vector4 Position;
}
public class Triangle
{
[device, buffer(0)]
public float[] Output;
[ShaderStage(Stage.Vertex)]
public VertexOutput Vertex([vertex_id] uint id)
{
VertexOutput output = new VertexOutput();
output.Position = new Vector4(0, 0, 0, 1);
return output;
}
[ShaderStage(Stage.Fragment)]
public Vector4 Fragment(VertexOutput input)
{
return new Vector4(1, 0.4f, 0.7f, 1);
}
// Workgroup size is set by the host
[ShaderStage(Stage.Compute)]
public void Compute([thread_position_in_grid] UInt3 id)
{
}
}
using YotsubaShader.Language;
using YotsubaShader.SpirvSyntax;
using YotsubaShader.SpirvSyntax.Resources;
public struct VertexOutput
{
[BuiltIn_Position]
public Vector4 Position;
}
public class Triangle
{
[StorageBuffer, DescriptorSet(0), @Binding(0)]
public float[] Output;
[ShaderStage(Stage.Vertex)]
public VertexOutput Vertex([BuiltIn_VertexIndex] uint id)
{
VertexOutput output = new VertexOutput();
output.Position = new Vector4(0, 0, 0, 1);
return output;
}
[ShaderStage(Stage.Fragment)]
public Vector4 Fragment(VertexOutput input)
{
return new Vector4(1, 0.4f, 0.7f, 1);
}
// Workgroup size is set by the host
[ShaderStage(Stage.Compute)]
public void Compute([BuiltIn_GlobalInvocationId] UInt3 id)
{
}
}
using YotsubaShader.Language;
using YotsubaShader.WgslSyntax;
using YotsubaShader.WgslSyntax.Resources;
public struct VertexOutput
{
[@builtin_position]
public Vector4 Position;
}
public class Triangle
{
[storage(storage_use.read_write), @group(0), @binding(0)]
public float[] Output;
[ShaderStage(Stage.Vertex)]
public VertexOutput Vertex([@builtin_vertex_index] uint id)
{
VertexOutput output = new VertexOutput();
output.Position = new Vector4(0, 0, 0, 1);
return output;
}
[ShaderStage(Stage.Fragment)]
public Vector4 Fragment(VertexOutput input)
{
return new Vector4(1, 0.4f, 0.7f, 1);
}
[ShaderStage(Stage.Compute)]
[@workgroup_size(8, 8, 1)]
public void Compute([@builtin_global_invocation_id] UInt3 id)
{
}
}
These names deliberately resemble their source languages. In C#, WGSL-inspired attributes use @ in the identifier, as in [@builtin_position], while remaining valid C#.
Resources in five syntaxes#
Uniform buffers and binding slots follow the same pattern.
using YotsubaShader.HlslSyntax;
using YotsubaShader.HlslSyntax.Resources;
public class Lighting
{
[cbuffer]
[register(RegisterPrefix.b, 0)]
public Material Material;
}
using YotsubaShader.GlslSyntax;
using YotsubaShader.GlslSyntax.Resources;
public class Lighting
{
[uniform]
[layout(set: 0, binding: 0)]
public Material Material;
}
using YotsubaShader.MslSyntax;
using YotsubaShader.MslSyntax.Resources;
public class Lighting
{
[constant]
[buffer(0)]
public Material Material;
}
using YotsubaShader.SpirvSyntax;
using YotsubaShader.SpirvSyntax.Resources;
public class Lighting
{
[Uniform]
[DescriptorSet(0)]
[Binding(0)]
public Material Material;
}
using YotsubaShader.WgslSyntax;
using YotsubaShader.WgslSyntax.Resources;
public class Lighting
{
[uniform]
[@group(0)]
[@binding(0)]
public Material Material;
}
The shared [Uniform], [StorageBuffer] and [Binding] attributes in YotsubaShader.Language work in every syntax as well.
Which one should I pick?#
- Your team already knows a shading language: use its syntax. Nothing in the IR depends on the syntax.
- You target a single API: the matching syntax reads most naturally (MSL for Metal, SPIR-V for Vulkan), but it is a convenience, not a requirement.
- You are starting fresh: the HLSL syntax is a good first choice. The relationship between stages, inputs, position and fragment color is easy to see.
Equivalence#
Each syntax is meant to be semantically equivalent to the others. The frontend already maps the position and vertex-index attributes of all five syntaxes to the same IR built-ins. See the full table in Built-in values across syntaxes.
