Editor/Prism/Core/Capability.cs

Defines a ShaderModel value type and a Capability enum plus a Capabilities lookup class. ShaderModel stores major/minor version, supports comparison, parsing and stringification. Capability lists discrete GPU/compiler features and Capabilities exposes minimum shader model, legal stages, availability on the project target, and user-facing descriptions.

Reflection
namespace Editor.Prism.Core;

/// <summary>
/// A shader model version. s&amp;box compiles every shader at <see cref="Sm6_0"/> (Vulkan flavour),
/// so anything above that is an error long before a compile is attempted.
/// </summary>
public readonly record struct ShaderModel( byte Major, byte Minor ) : IComparable<ShaderModel>
{
	/// <summary>Shader model 5.0.</summary>
	public static readonly ShaderModel Sm5_0 = new( 5, 0 );
	/// <summary>Shader model 6.0. The s&amp;box compile target.</summary>
	public static readonly ShaderModel Sm6_0 = new( 6, 0 );
	/// <summary>Shader model 6.1.</summary>
	public static readonly ShaderModel Sm6_1 = new( 6, 1 );
	/// <summary>Shader model 6.2.</summary>
	public static readonly ShaderModel Sm6_2 = new( 6, 2 );
	/// <summary>Shader model 6.3.</summary>
	public static readonly ShaderModel Sm6_3 = new( 6, 3 );
	/// <summary>Shader model 6.4.</summary>
	public static readonly ShaderModel Sm6_4 = new( 6, 4 );
	/// <summary>Shader model 6.5.</summary>
	public static readonly ShaderModel Sm6_5 = new( 6, 5 );
	/// <summary>Shader model 6.6.</summary>
	public static readonly ShaderModel Sm6_6 = new( 6, 6 );
	/// <summary>Shader model 6.7.</summary>
	public static readonly ShaderModel Sm6_7 = new( 6, 7 );
	/// <summary>Shader model 6.8.</summary>
	public static readonly ShaderModel Sm6_8 = new( 6, 8 );

	/// <summary>The model every s&amp;box shader is compiled at. Everything is validated against this.</summary>
	public static readonly ShaderModel Target = Sm6_0;

	/// <inheritdoc/>
	public int CompareTo( ShaderModel other )
	{
		var major = Major.CompareTo( other.Major );
		return major != 0 ? major : Minor.CompareTo( other.Minor );
	}

	/// <summary>True when the left model is at most the right one.</summary>
	public static bool operator <=( ShaderModel a, ShaderModel b ) => a.CompareTo( b ) <= 0;
	/// <summary>True when the left model is at least the right one.</summary>
	public static bool operator >=( ShaderModel a, ShaderModel b ) => a.CompareTo( b ) >= 0;
	/// <summary>True when the left model is strictly lower.</summary>
	public static bool operator <( ShaderModel a, ShaderModel b ) => a.CompareTo( b ) < 0;
	/// <summary>True when the left model is strictly higher.</summary>
	public static bool operator >( ShaderModel a, ShaderModel b ) => a.CompareTo( b ) > 0;

	/// <summary>Parse a <c>"6.0"</c> style string. Returns false on anything else.</summary>
	public static bool TryParse( string text, out ShaderModel model )
	{
		model = default;
		if ( string.IsNullOrWhiteSpace( text ) ) return false;

		var parts = text.Trim().Split( '.' );
		if ( parts.Length != 2 ) return false;
		if ( !byte.TryParse( parts[0], out var major ) ) return false;
		if ( !byte.TryParse( parts[1], out var minor ) ) return false;

		model = new ShaderModel( major, minor );
		return true;
	}

	/// <inheritdoc/>
	public override string ToString() => $"{Major}.{Minor}";
}

/// <summary>
/// A discrete hardware or compiler feature a node may require. Every capability carries a minimum
/// shader model and the set of stages it is legal in; both the graph validator and the intrinsic
/// gate read the same table, so "requires SM 6.5" is reported identically everywhere.
/// </summary>
public enum Capability
{
	/// <summary>No requirement.</summary>
	None,
	/// <summary>Screen-space derivatives: <c>ddx</c>, <c>ddy</c>, <c>fwidth</c>. Pixel stage only.</summary>
	PixelDerivatives,
	/// <summary>Fragment discard: <c>clip</c> / <c>discard</c>. Pixel stage only.</summary>
	Discard,
	/// <summary>Texture sampling that computes its own mip level. Requires derivatives.</summary>
	ImplicitLodSampling,
	/// <summary>Texture sampling with an explicit mip level or gradient. Legal in every stage.</summary>
	ExplicitLodSampling,
	/// <summary>Four-texel <c>Gather</c> family.</summary>
	TextureGather,
	/// <summary>Comparison sampling for shadow lookups.</summary>
	ComparisonSampling,
	/// <summary>Real <c>for</c>/<c>while</c> loops in the emitted code.</summary>
	Loops,
	/// <summary>Real branching, where only the taken side executes.</summary>
	DynamicBranching,
	/// <summary>Integer arithmetic.</summary>
	IntegerOps,
	/// <summary>Bitwise operators and the <c>countbits</c> family.</summary>
	BitwiseOps,
	/// <summary>Native 16-bit types. Requires <c>-enable-16bit-types</c> and SM 6.2.</summary>
	Half16,
	/// <summary>Double precision arithmetic.</summary>
	DoublePrecision,
	/// <summary>SM 6.0 wave intrinsics.</summary>
	WaveOps,
	/// <summary><c>WaveMatch</c> and the multi-prefix family.</summary>
	WaveMatch,
	/// <summary>Quad shuffle intrinsics.</summary>
	QuadOps,
	/// <summary><c>QuadAny</c> / <c>QuadAll</c>.</summary>
	QuadAnyAll,
	/// <summary>Writable resources. Compute only.</summary>
	Uav,
	/// <summary>Group-shared memory. Compute only.</summary>
	GroupShared,
	/// <summary>Interlocked operations. Compute only.</summary>
	Atomics,
	/// <summary>64-bit interlocked operations.</summary>
	Atomics64,
	/// <summary>Memory barriers. Compute only.</summary>
	Barriers,
	/// <summary>Per-sample attribute evaluation.</summary>
	EvaluateAttribute,
	/// <summary><c>SV_Barycentrics</c> and <c>GetAttributeAtVertex</c>.</summary>
	BarycentricAttributes,
	/// <summary>Pack/unpack intrinsics.</summary>
	PackUnpack,
	/// <summary>Dot-product-accumulate intrinsics.</summary>
	DotProductAccumulate,
	/// <summary>Bindless resource heaps.</summary>
	ResourceDescriptorHeap,
	/// <summary>Ray tracing dispatch.</summary>
	RayTracing,
	/// <summary>Inline ray tracing via <c>RayQuery</c>.</summary>
	RayQuery,
	/// <summary>Mesh and amplification shaders. No s&amp;box program type exists for these.</summary>
	MeshShaders,
	/// <summary>Custom VS to PS interpolators.</summary>
	Interpolators,
	/// <summary>Static/dynamic/feature combos.</summary>
	Combos,
	/// <summary>More than one render target.</summary>
	MultipleRenderTargets,
	/// <summary>Writing <c>SV_Depth</c>.</summary>
	DepthOutput,
	/// <summary>Slang-style methods on structs. Available in the SboxSlang dialect, not in strict HLSL 2021.</summary>
	StructMethods,
	/// <summary>Geometry shader stage.</summary>
	GeometryStage
}

/// <summary>The capability table: minimum shader model, legal stages and a user-facing description.</summary>
public static class Capabilities
{
	/// <summary>Minimum shader model required by a capability.</summary>
	public static ShaderModel MinShaderModel( Capability capability ) => capability switch
	{
		Capability.Half16 => ShaderModel.Sm6_2,
		Capability.BarycentricAttributes => ShaderModel.Sm6_1,
		Capability.DotProductAccumulate => ShaderModel.Sm6_4,
		Capability.WaveMatch => ShaderModel.Sm6_5,
		Capability.RayTracing => ShaderModel.Sm6_3,
		Capability.RayQuery => ShaderModel.Sm6_5,
		Capability.MeshShaders => ShaderModel.Sm6_5,
		Capability.QuadAnyAll => ShaderModel.Sm6_7,
		Capability.PackUnpack => ShaderModel.Sm6_6,
		Capability.Atomics64 => ShaderModel.Sm6_6,
		Capability.ResourceDescriptorHeap => ShaderModel.Sm6_6,
		Capability.WaveOps => ShaderModel.Sm6_0,
		Capability.QuadOps => ShaderModel.Sm6_0,
		_ => ShaderModel.Sm5_0
	};

	/// <summary>Stages in which a capability is legal.</summary>
	public static StageMask Stages( Capability capability ) => capability switch
	{
		Capability.PixelDerivatives => StageMask.Pixel,
		Capability.Discard => StageMask.Pixel,
		Capability.ImplicitLodSampling => StageMask.Pixel,
		Capability.EvaluateAttribute => StageMask.Pixel,
		Capability.BarycentricAttributes => StageMask.Pixel,
		Capability.MultipleRenderTargets => StageMask.Pixel,
		Capability.DepthOutput => StageMask.Pixel,
		Capability.Uav => StageMask.Compute,
		Capability.GroupShared => StageMask.Compute,
		Capability.Atomics => StageMask.Compute,
		Capability.Atomics64 => StageMask.Compute,
		Capability.Barriers => StageMask.Compute,
		Capability.Interpolators => StageMask.VertexPixel,
		Capability.GeometryStage => StageMask.Geometry,
		_ => StageMask.All
	};

	/// <summary>True when the capability is usable on the s&amp;box compile target.</summary>
	public static bool IsAvailableOnTarget( Capability capability ) =>
		MinShaderModel( capability ) <= ShaderModel.Target;

	/// <summary>Short description used in diagnostics and tooltips.</summary>
	public static string Describe( Capability capability ) => capability switch
	{
		Capability.PixelDerivatives => "screen-space derivatives",
		Capability.Discard => "fragment discard",
		Capability.ImplicitLodSampling => "implicit-LOD texture sampling",
		Capability.ExplicitLodSampling => "explicit-LOD texture sampling",
		Capability.TextureGather => "texture gather",
		Capability.ComparisonSampling => "comparison sampling",
		Capability.Loops => "loops",
		Capability.DynamicBranching => "dynamic branching",
		Capability.IntegerOps => "integer arithmetic",
		Capability.BitwiseOps => "bitwise operations",
		Capability.Half16 => "native 16-bit types",
		Capability.DoublePrecision => "double precision",
		Capability.WaveOps => "wave intrinsics",
		Capability.WaveMatch => "WaveMatch / wave multi-prefix",
		Capability.QuadOps => "quad intrinsics",
		Capability.QuadAnyAll => "QuadAny / QuadAll",
		Capability.Uav => "writable resources",
		Capability.GroupShared => "groupshared memory",
		Capability.Atomics => "interlocked operations",
		Capability.Atomics64 => "64-bit interlocked operations",
		Capability.Barriers => "memory barriers",
		Capability.EvaluateAttribute => "per-sample attribute evaluation",
		Capability.BarycentricAttributes => "barycentric attributes",
		Capability.PackUnpack => "pack / unpack intrinsics",
		Capability.DotProductAccumulate => "dot-product-accumulate intrinsics",
		Capability.ResourceDescriptorHeap => "bindless descriptor heaps",
		Capability.RayTracing => "ray tracing",
		Capability.RayQuery => "inline ray tracing",
		Capability.MeshShaders => "mesh shaders",
		Capability.Interpolators => "custom interpolators",
		Capability.Combos => "shader combos",
		Capability.MultipleRenderTargets => "multiple render targets",
		Capability.DepthOutput => "depth output",
		Capability.StructMethods => "struct methods",
		Capability.GeometryStage => "geometry shaders",
		_ => "none"
	};
}