Skip to content

Canvas Shaders

Ryan Andersen edited this page Jul 10, 2024 · 1 revision

SimulationFramework can compile a subset of .NET CIL into shader languages used by the graphics backend (right now, just glsl).

Just Show Me a Shader!

Here's a simple shader:

using SimulationFramework;
using SimulationFramework.Drawing;
using SimulationFramework.Drawing.Shaders;

class MyShader : CanvasShader 
{
    public override ColorF GetPixelColor(Vector2 position)
    {
        return ColorF.Red;
    }
}

This shader simply fills everything red. Every shader must override the GetPixelColor(Vector2) method in the CanvasShader class. This is the entry point to your shader. It's called once per pixel for every shape it's used to draw.

To use a shader, just pass an instance of it to ICanvas.Fill and draw some shapes:

public override void OnRender(ICanvas canvas) 
{
    MyShader myShader = new MyShader();
    canvas.Fill(myShader);
    canvas.DrawCircle(Mouse.Position, 50);
}

Shader Intrinsics

Shader langauges usually expose most functionality through a set of intrinsic functions. These are accessable via the ShaderIntrinsics class:

class MyShader : CanvasShader 
{
    public override ColorF GetPixelColor(Vector2 position)
    {
        return new ColorF(ShaderIntrinsics.Sin(position.X / 200f), 0, 0, 1);
    }
}

⚠️ Right now, only a limited subset of shader intrinsics are supported.

Passing Variables to a Shader

You can pass values to the shader through fields on the shader class.

using SimulationFramework;
using SimulationFramework.Drawing;
using SimulationFramework.Drawing.Shaders;

class MyShader : CanvasShader 
{
    public ColorF color;
    public override ColorF GetPixelColor(Vector2 position) 
    {
        return color;
    }
}

To pass the value to the shader, just set the field!

public override void OnRender(ICanvas canvas) 
{
    MyShader myShader = new MyShader();
    myShader.color = ColorF.Orange;
    canvas.Fill(myShader);
    canvas.DrawCircle(Mouse.Position, 50);
}

Arrays

you can pass arrays of values to shaders.

‼️‼️ There are currently alignment problems with shader fields that are 12 bytes wide (ie Circle, Vector3). Don't pass values of these types through arrays as it will not work properly

class MyShader : CanvasShader
{
    public Vector4[] circles;

    public override ColorF GetPixelColor(Vector2 position)
    {
        ColorF color = ColorF.Black;
        float minDist = float.PositiveInfinity;
        for (int i = 0; i < circles.Length; i++)
        {
            Circle c = default;
            c.Position = new(circles[i].X, circles[i].Y);
            c.Radius = circles[i].Z;
            float d = MathF.Abs(c.SignedDistance(position));
            if (d < minDist)
            {
                minDist = d;
            }
        }
        return color = new(1 - (minDist / 10f) * (minDist / 10f), 0, 0);
    }
}

Textures

You can sample textures from shaders.

⚠️ Right now, texture sampling coordinates are in UV coordinates (ie 0-1). In future versions, texture sampling will use the texture's pixel coordinates. (UV sampling will still be available via SampleUV). To convert from a texture's pixel coordinates to UV coordinates, divide by it's size on each axis.

class MyShader : CanvasShader
{
    public ITexture myTexture;

    public override ColorF GetPixelColor(Vector2 position)
    {
        return myTexture.Sample(position * (1f/512f));
    }
}

Limitations

Most typical C# code cannot be reasonably translated into shader code. In these cases, an exception will be thrown when you try to use the shader.

These limitations include:

  • Reference types are not allowed (classes, ref structs)
    • limited exceptions include arrays and textures, which can be passed to a shader via a field but not assigned to variables or passed into methods.

Allowed types:

  • bool
  • int
  • float
  • Vector2
  • Vector3
  • Vector4
  • ColorF
  • struct types consisting of only the above types

Temporary limitations

Canvas Shaders are currently in alpha and are therefore not feature complete. This means there are temporary limitations that should be lifted by the full release:

  • Early returns are not supported in most cases.
  • Most types (even primitives) are not supported.
  • Only a limited set of intrinsics are supported.