Defines shader-related enums and a readonly record struct ShaderType that represents scalar/vector/matrix/object/struct shader types, provides factories (Vec/Mat/Obj/Struct), queries (IsScalar/IsTexture/etc.), string spellings (Hlsl/Slang), parsing from text, and many predefined well-known types.
using System.ComponentModel;
namespace Editor.Prism.Core;
/// <summary>The component type of a scalar, vector or matrix.</summary>
public enum ScalarKind
{
/// <summary>No value.</summary>
Void,
/// <summary>32-bit boolean.</summary>
Bool,
/// <summary>32-bit signed integer.</summary>
Int,
/// <summary>32-bit unsigned integer.</summary>
UInt,
/// <summary>16-bit float. Parses everywhere, but s&box compiles at SM 6.0 so it lowers to float.</summary>
Half,
/// <summary>32-bit float. The default for everything in a shader graph.</summary>
Float,
/// <summary>64-bit float.</summary>
Double
}
/// <summary>The broad shape of a <see cref="ShaderType"/>.</summary>
public enum TypeClass
{
/// <summary>No type. This is <c>default(ShaderType)</c>.</summary>
Void,
/// <summary>A single component, e.g. <c>float</c>.</summary>
Scalar,
/// <summary>1..4 components, e.g. <c>float3</c>.</summary>
Vector,
/// <summary>RowsxCols components, e.g. <c>float3x3</c>.</summary>
Matrix,
/// <summary>An opaque resource object, e.g. <c>Texture2D</c> or <c>SamplerState</c>.</summary>
Object,
/// <summary>A named user struct.</summary>
Struct
}
/// <summary>An opaque shader resource type. Never implicitly convertible to or from anything numeric.</summary>
public enum ObjectKind
{
/// <summary>Not an object type.</summary>
None,
/// <summary>1D texture.</summary>
Texture1D,
/// <summary>1D texture array.</summary>
Texture1DArray,
/// <summary>2D texture. The workhorse.</summary>
Texture2D,
/// <summary>2D texture array.</summary>
Texture2DArray,
/// <summary>Multisampled 2D texture.</summary>
Texture2DMS,
/// <summary>3D volume texture.</summary>
Texture3D,
/// <summary>Cube map.</summary>
TextureCube,
/// <summary>Cube map array.</summary>
TextureCubeArray,
/// <summary>Filtering sampler.</summary>
SamplerState,
/// <summary>Comparison sampler, used for shadow lookups.</summary>
SamplerComparisonState,
/// <summary>Typed read-only buffer.</summary>
Buffer,
/// <summary>Untyped read-only buffer.</summary>
ByteAddressBuffer,
/// <summary>Structured read-only buffer.</summary>
StructuredBuffer,
/// <summary>Writable 2D texture (compute only).</summary>
RWTexture2D,
/// <summary>Writable 3D texture (compute only).</summary>
RWTexture3D,
/// <summary>Writable typed buffer (compute only).</summary>
RWBuffer,
/// <summary>Writable structured buffer (compute only).</summary>
RWStructuredBuffer,
/// <summary>Writable untyped buffer (compute only).</summary>
RWByteAddressBuffer
}
/// <summary>
/// The Prism type lattice. Unlike the built-in ShaderGraph — whose entire type model is an
/// <c>int Components</c> in 0..4 — this is a real type: scalar kind, shape, dimensions and object kind.
/// <para>
/// Conventions: a vector stores its width in <see cref="Rows"/> with <see cref="Cols"/> = 1;
/// a 1-wide vector canonicalises to <see cref="TypeClass.Scalar"/> so <c>float</c> and <c>float1</c>
/// are the same value. <c>default(ShaderType)</c> is <see cref="Void"/>.
/// </para>
/// </summary>
public readonly record struct ShaderType(
TypeClass Class, ScalarKind Scalar, byte Rows, byte Cols, ObjectKind Object, string StructName )
{
// ---- well-known types -------------------------------------------------
/// <summary>The empty type.</summary>
public static readonly ShaderType Void = default;
/// <summary><c>bool</c></summary>
public static readonly ShaderType Bool = Vec( ScalarKind.Bool, 1 );
/// <summary><c>bool2</c></summary>
public static readonly ShaderType Bool2 = Vec( ScalarKind.Bool, 2 );
/// <summary><c>bool3</c></summary>
public static readonly ShaderType Bool3 = Vec( ScalarKind.Bool, 3 );
/// <summary><c>bool4</c></summary>
public static readonly ShaderType Bool4 = Vec( ScalarKind.Bool, 4 );
/// <summary><c>int</c></summary>
public static readonly ShaderType Int = Vec( ScalarKind.Int, 1 );
/// <summary><c>int2</c></summary>
public static readonly ShaderType Int2 = Vec( ScalarKind.Int, 2 );
/// <summary><c>int3</c></summary>
public static readonly ShaderType Int3 = Vec( ScalarKind.Int, 3 );
/// <summary><c>int4</c></summary>
public static readonly ShaderType Int4 = Vec( ScalarKind.Int, 4 );
/// <summary><c>uint</c></summary>
public static readonly ShaderType UInt = Vec( ScalarKind.UInt, 1 );
/// <summary><c>uint2</c></summary>
public static readonly ShaderType UInt2 = Vec( ScalarKind.UInt, 2 );
/// <summary><c>uint3</c></summary>
public static readonly ShaderType UInt3 = Vec( ScalarKind.UInt, 3 );
/// <summary><c>uint4</c></summary>
public static readonly ShaderType UInt4 = Vec( ScalarKind.UInt, 4 );
/// <summary><c>half</c></summary>
public static readonly ShaderType Half = Vec( ScalarKind.Half, 1 );
/// <summary><c>half2</c></summary>
public static readonly ShaderType Half2 = Vec( ScalarKind.Half, 2 );
/// <summary><c>half3</c></summary>
public static readonly ShaderType Half3 = Vec( ScalarKind.Half, 3 );
/// <summary><c>half4</c></summary>
public static readonly ShaderType Half4 = Vec( ScalarKind.Half, 4 );
/// <summary><c>float</c></summary>
public static readonly ShaderType Float = Vec( ScalarKind.Float, 1 );
/// <summary><c>float2</c></summary>
public static readonly ShaderType Float2 = Vec( ScalarKind.Float, 2 );
/// <summary><c>float3</c></summary>
public static readonly ShaderType Float3 = Vec( ScalarKind.Float, 3 );
/// <summary><c>float4</c></summary>
public static readonly ShaderType Float4 = Vec( ScalarKind.Float, 4 );
/// <summary><c>double</c></summary>
public static readonly ShaderType Double = Vec( ScalarKind.Double, 1 );
/// <summary><c>float2x2</c></summary>
public static readonly ShaderType Float2x2 = Mat( ScalarKind.Float, 2, 2 );
/// <summary><c>float3x3</c></summary>
public static readonly ShaderType Float3x3 = Mat( ScalarKind.Float, 3, 3 );
/// <summary><c>float4x3</c></summary>
public static readonly ShaderType Float4x3 = Mat( ScalarKind.Float, 4, 3 );
/// <summary><c>float3x4</c></summary>
public static readonly ShaderType Float3x4 = Mat( ScalarKind.Float, 3, 4 );
/// <summary><c>float4x4</c></summary>
public static readonly ShaderType Float4x4 = Mat( ScalarKind.Float, 4, 4 );
/// <summary><c>Texture2D</c></summary>
public static readonly ShaderType Texture2D = Obj( ObjectKind.Texture2D );
/// <summary><c>Texture2DArray</c></summary>
public static readonly ShaderType Texture2DArray = Obj( ObjectKind.Texture2DArray );
/// <summary><c>Texture3D</c></summary>
public static readonly ShaderType Texture3D = Obj( ObjectKind.Texture3D );
/// <summary><c>TextureCube</c></summary>
public static readonly ShaderType TextureCube = Obj( ObjectKind.TextureCube );
/// <summary><c>TextureCubeArray</c></summary>
public static readonly ShaderType TextureCubeArray = Obj( ObjectKind.TextureCubeArray );
/// <summary><c>SamplerState</c></summary>
public static readonly ShaderType Sampler = Obj( ObjectKind.SamplerState );
/// <summary><c>SamplerComparisonState</c></summary>
public static readonly ShaderType SamplerComparison = Obj( ObjectKind.SamplerComparisonState );
// ---- factories --------------------------------------------------------
/// <summary>Build a scalar or vector type. <paramref name="components"/> is clamped to 1..4.</summary>
public static ShaderType Vec( ScalarKind scalar, int components )
{
if ( scalar == ScalarKind.Void ) return Void;
var n = (byte)Math.Clamp( components, 1, 4 );
return new ShaderType( n == 1 ? TypeClass.Scalar : TypeClass.Vector, scalar, n, 1, ObjectKind.None, null );
}
/// <summary>Build a matrix type. Dimensions are clamped to 1..4.</summary>
public static ShaderType Mat( ScalarKind scalar, int rows, int cols )
{
if ( scalar == ScalarKind.Void ) return Void;
return new ShaderType( TypeClass.Matrix, scalar,
(byte)Math.Clamp( rows, 1, 4 ), (byte)Math.Clamp( cols, 1, 4 ), ObjectKind.None, null );
}
/// <summary>Build an opaque resource type.</summary>
public static ShaderType Obj( ObjectKind kind )
{
if ( kind == ObjectKind.None ) return Void;
return new ShaderType( TypeClass.Object, ScalarKind.Void, 0, 0, kind, null );
}
/// <summary>Build a named user struct type.</summary>
public static ShaderType Struct( string name )
{
if ( string.IsNullOrEmpty( name ) ) return Void;
return new ShaderType( TypeClass.Struct, ScalarKind.Void, 0, 0, ObjectKind.None, name );
}
// ---- queries ----------------------------------------------------------
/// <summary>Total component count: 1 for a scalar, N for a vector, RxC for a matrix, 0 otherwise.</summary>
public int Components => Class switch
{
TypeClass.Scalar => 1,
TypeClass.Vector => Rows,
TypeClass.Matrix => Rows * Cols,
_ => 0
};
/// <summary>True for the empty type.</summary>
public bool IsVoid => Class == TypeClass.Void;
/// <summary>True for a single-component numeric type.</summary>
public bool IsScalar => Class == TypeClass.Scalar;
/// <summary>True for a 2..4 component numeric type.</summary>
public bool IsVector => Class == TypeClass.Vector;
/// <summary>True for a scalar or a vector.</summary>
public bool IsScalarOrVector => Class is TypeClass.Scalar or TypeClass.Vector;
/// <summary>True for a matrix type.</summary>
public bool IsMatrix => Class == TypeClass.Matrix;
/// <summary>True for an opaque resource type.</summary>
public bool IsObject => Class == TypeClass.Object;
/// <summary>True for a named user struct.</summary>
public bool IsStruct => Class == TypeClass.Struct;
/// <summary>True when the type has numeric components (scalar, vector or matrix).</summary>
public bool IsNumeric => Class is TypeClass.Scalar or TypeClass.Vector or TypeClass.Matrix;
/// <summary>True when the component type is a float, half or double.</summary>
public bool IsFloatingPoint => Scalar is ScalarKind.Half or ScalarKind.Float or ScalarKind.Double;
/// <summary>True when the component type is an int or uint.</summary>
public bool IsIntegral => Scalar is ScalarKind.Int or ScalarKind.UInt;
/// <summary>True when the component type is a bool.</summary>
public bool IsBoolean => Scalar == ScalarKind.Bool;
/// <summary>True for any texture object.</summary>
public bool IsTexture => Class == TypeClass.Object && Object is
ObjectKind.Texture1D or ObjectKind.Texture1DArray or ObjectKind.Texture2D or ObjectKind.Texture2DArray or
ObjectKind.Texture2DMS or ObjectKind.Texture3D or ObjectKind.TextureCube or ObjectKind.TextureCubeArray or
ObjectKind.RWTexture2D or ObjectKind.RWTexture3D;
/// <summary>True for either sampler kind.</summary>
public bool IsSampler => Class == TypeClass.Object &&
Object is ObjectKind.SamplerState or ObjectKind.SamplerComparisonState;
/// <summary>True for a buffer object of any kind.</summary>
public bool IsBuffer => Class == TypeClass.Object && Object is
ObjectKind.Buffer or ObjectKind.ByteAddressBuffer or ObjectKind.StructuredBuffer or
ObjectKind.RWBuffer or ObjectKind.RWStructuredBuffer or ObjectKind.RWByteAddressBuffer;
/// <summary>True for a writable (UAV) resource. These require the Uav capability and a compute stage.</summary>
public bool IsWritable => Class == TypeClass.Object && Object is
ObjectKind.RWTexture2D or ObjectKind.RWTexture3D or ObjectKind.RWBuffer or
ObjectKind.RWStructuredBuffer or ObjectKind.RWByteAddressBuffer;
/// <summary>The single-component type behind this one, e.g. <c>float3</c> gives <c>float</c>.</summary>
/// <remarks>
/// Hidden from reflection-driven UI. This property returns its own declaring type, and
/// <see cref="ShaderType"/> is a plain struct with no <c>ControlWidget</c> registered, so a
/// <c>ControlSheet</c> that meets one — via <c>ConstantNode.ValueType</c> or a blackboard
/// parameter's type — expands it, then expands the result, forever. That recursion happens inside
/// engine code and ends as an uncatchable <c>StackOverflowException</c> that kills all of s&box.
/// A scalar's <c>ScalarType</c> is itself, so the chain never even changes value.
/// </remarks>
[Hide, Browsable( false ), JsonIgnore]
public ShaderType ScalarType => IsNumeric ? Vec( Scalar, 1 ) : Void;
/// <summary>The same shape with a different component kind.</summary>
public ShaderType WithScalar( ScalarKind scalar ) => Class switch
{
TypeClass.Scalar or TypeClass.Vector => Vec( scalar, Rows ),
TypeClass.Matrix => Mat( scalar, Rows, Cols ),
_ => this
};
/// <summary>The same component kind widened or narrowed to <paramref name="components"/> (scalars/vectors only).</summary>
public ShaderType WithComponents( int components ) =>
IsScalarOrVector ? Vec( Scalar, components ) : this;
// ---- spelling ---------------------------------------------------------
/// <summary>How this type is spelled in HLSL.</summary>
public string Hlsl => Class switch
{
TypeClass.Void => "void",
TypeClass.Scalar => ScalarName( Scalar ),
TypeClass.Vector => $"{ScalarName( Scalar )}{Rows}",
TypeClass.Matrix => $"{ScalarName( Scalar )}{Rows}x{Cols}",
TypeClass.Object => ObjectName( Object ),
TypeClass.Struct => StructName ?? "void",
_ => "void"
};
/// <summary>
/// How this type is spelled in Slang. Slang is an HLSL superset, so the spellings match today —
/// this stays a separate member so the Slang backend can diverge without touching call sites.
/// </summary>
public string Slang => Class switch
{
TypeClass.Void => "void",
TypeClass.Scalar => ScalarName( Scalar ),
TypeClass.Vector => $"{ScalarName( Scalar )}{Rows}",
TypeClass.Matrix => $"{ScalarName( Scalar )}{Rows}x{Cols}",
TypeClass.Object => ObjectName( Object ),
TypeClass.Struct => StructName ?? "void",
_ => "void"
};
/// <inheritdoc/>
public override string ToString() => Hlsl;
/// <summary>HLSL keyword for a scalar kind.</summary>
public static string ScalarName( ScalarKind kind ) => kind switch
{
ScalarKind.Bool => "bool",
ScalarKind.Int => "int",
ScalarKind.UInt => "uint",
ScalarKind.Half => "half",
ScalarKind.Float => "float",
ScalarKind.Double => "double",
_ => "void"
};
/// <summary>HLSL keyword for an object kind.</summary>
public static string ObjectName( ObjectKind kind ) => kind switch
{
ObjectKind.Texture1D => "Texture1D",
ObjectKind.Texture1DArray => "Texture1DArray",
ObjectKind.Texture2D => "Texture2D",
ObjectKind.Texture2DArray => "Texture2DArray",
ObjectKind.Texture2DMS => "Texture2DMS",
ObjectKind.Texture3D => "Texture3D",
ObjectKind.TextureCube => "TextureCube",
ObjectKind.TextureCubeArray => "TextureCubeArray",
ObjectKind.SamplerState => "SamplerState",
ObjectKind.SamplerComparisonState => "SamplerComparisonState",
ObjectKind.Buffer => "Buffer",
ObjectKind.ByteAddressBuffer => "ByteAddressBuffer",
ObjectKind.StructuredBuffer => "StructuredBuffer",
ObjectKind.RWTexture2D => "RWTexture2D",
ObjectKind.RWTexture3D => "RWTexture3D",
ObjectKind.RWBuffer => "RWBuffer",
ObjectKind.RWStructuredBuffer => "RWStructuredBuffer",
ObjectKind.RWByteAddressBuffer => "RWByteAddressBuffer",
_ => "void"
};
// ---- parsing ----------------------------------------------------------
/// <summary>
/// Parse a concrete type spelling such as <c>float3</c>, <c>float3x3</c>, <c>bool</c> or <c>Texture2D</c>.
/// Type variables (<c>T</c>, <c>vecN</c>, <c>any</c>, <c>float{N}</c>) are NOT accepted here — see
/// <see cref="TypeRules.IsTypeVariable"/>.
/// </summary>
public static bool TryParse( string text, out ShaderType type )
{
type = Void;
if ( string.IsNullOrWhiteSpace( text ) ) return false;
var s = text.Trim();
// Object types are matched case-insensitively against the canonical spellings.
foreach ( var kind in s_objectKinds )
{
if ( string.Equals( s, ObjectName( kind ), StringComparison.OrdinalIgnoreCase ) )
{
type = Obj( kind );
return true;
}
}
if ( string.Equals( s, "void", StringComparison.OrdinalIgnoreCase ) )
{
type = Void;
return true;
}
if ( string.Equals( s, "sampler", StringComparison.OrdinalIgnoreCase ) )
{
type = Sampler;
return true;
}
foreach ( var kind in s_scalarKinds )
{
var name = ScalarName( kind );
if ( !s.StartsWith( name, StringComparison.OrdinalIgnoreCase ) ) continue;
var tail = s[name.Length..];
if ( tail.Length == 0 )
{
type = Vec( kind, 1 );
return true;
}
if ( tail.Length == 1 && tail[0] >= '1' && tail[0] <= '4' )
{
type = Vec( kind, tail[0] - '0' );
return true;
}
if ( tail.Length == 3 && tail[1] == 'x' &&
tail[0] >= '1' && tail[0] <= '4' && tail[2] >= '1' && tail[2] <= '4' )
{
type = Mat( kind, tail[0] - '0', tail[2] - '0' );
return true;
}
}
return false;
}
/// <summary>Parse a concrete type spelling, returning <see cref="Void"/> when unrecognised.</summary>
public static ShaderType Parse( string text ) => TryParse( text, out var t ) ? t : Void;
static readonly ScalarKind[] s_scalarKinds =
[
ScalarKind.Double, ScalarKind.Float, ScalarKind.Half, ScalarKind.UInt, ScalarKind.Int, ScalarKind.Bool
];
static readonly ObjectKind[] s_objectKinds =
[
ObjectKind.Texture1DArray, ObjectKind.Texture1D, ObjectKind.Texture2DArray, ObjectKind.Texture2DMS,
ObjectKind.Texture2D, ObjectKind.Texture3D, ObjectKind.TextureCubeArray, ObjectKind.TextureCube,
ObjectKind.SamplerComparisonState, ObjectKind.SamplerState, ObjectKind.ByteAddressBuffer,
ObjectKind.StructuredBuffer, ObjectKind.Buffer, ObjectKind.RWTexture2D, ObjectKind.RWTexture3D,
ObjectKind.RWStructuredBuffer, ObjectKind.RWByteAddressBuffer, ObjectKind.RWBuffer
];
}