A proof-of-concept library which generates shader code from C#. Currently, the project can generate HLSL (D3D11), GLSL-330 (core-GL-compatible), GLSLES-300 (OpenGL ES-compatible), GLSL-450 (Vulkan-compatible), and Metal shader code from a single shader source specified in C#.
Writing shader code in C# could have quite a few benefits:
- Easily share type definitions between graphics code in C# and shaders.
- For example, one could re-use the same structure to describe the input to a vertex shader, as well as to store the actual vertex data in your C# program.
- Shader uniforms ("constant buffers") can be shared as well.
- Analysis done at code-generation time can be used to build extra metadata about the shaders, enabling reflection-like capabilities.
- The full vertex input specification can be generated when doing the C# analysis for code generation.
- The layouts and order for all global shader resources can be captured.
- Validation can be performed to ensure, for example, uniforms are multiples of 16-bytes in size, etc.
- C# refactoring tools can be used.
- C# niceties like inheritance, composition, partial declarations, etc. can be leveraged for easier shader writing (speculative).
Here is an example vertex and fragment shader, written in C# with ShaderGen:
using ShaderGen;
using static ShaderGen.ShaderBuiltins; // Important for Mul, Sample, etc.
[ShaderClass]
public class MinExample
{
public Matrix4x4 Projection;
public Matrix4x4 View;
public Matrix4x4 World;
public Texture2DResource SurfaceTexture;
public SamplerResource Sampler;
public struct VertexInput
{
[PositionSemantic] public Vector3 Position;
[TextureCoordinateSemantic] public Vector2 TextureCoord;
}
public struct FragmentInput
{
[SystemPositionSemanticAttribute] public Vector4 Position;
[TextureCoordinateSemantic] public Vector2 TextureCoord;
}
[VertexShader]
public FragmentInput VertexShaderFunc(VertexInput input)
{
FragmentInput output;
Vector4 worldPosition = Mul(World, new Vector4(input.Position, 1));
Vector4 viewPosition = Mul(View, worldPosition);
output.Position = Mul(Projection, viewPosition);
output.TextureCoord = input.TextureCoord;
return output;
}
[FragmentShader]
public Vector4 FragmentShaderFunc(FragmentInput input)
{
return Sample(SurfaceTexture, Sampler, input.TextureCoord);
}
}
Here is some representative output from the library (subject to change, etc.):
HLSL Vertex Shader
struct MinExample_VertexInput
{
float3 Position : POSITION0;
float2 TextureCoord : TEXCOORD0;
};
struct MinExample_FragmentInput
{
float4 Position : SV_Position;
float2 TextureCoord : TEXCOORD0;
};
cbuffer ProjectionBuffer : register(b0)
{
float4x4 Projection;
}
cbuffer ViewBuffer : register(b1)
{
float4x4 View;
}
cbuffer WorldBuffer : register(b2)
{
float4x4 World;
}
MinExample_FragmentInput VertexShaderFunc( MinExample_VertexInput input)
{
MinExample_FragmentInput output;
float4 worldPosition = mul(World, float4(input.Position, 1));
float4 viewPosition = mul(View, worldPosition);
output.Position = mul(Projection, viewPosition);
output.TextureCoord = input.TextureCoord;
return output;
}
HLSL Fragment Shader
struct MinExample_VertexInput
{
float3 Position : POSITION0;
float2 TextureCoord : TEXCOORD0;
};
struct MinExample_FragmentInput
{
float4 Position : SV_Position;
float2 TextureCoord : TEXCOORD0;
};
Texture2D SurfaceTexture : register(t0);
SamplerState Sampler : register(s0);
float4 FragmentShaderFunc( MinExample_FragmentInput input) : SV_Target
{
return SurfaceTexture.Sample(Sampler, input.TextureCoord);
}
GLSL (450) Vertex Shader