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.
namespace Editor.Prism.Core;
/// <summary>
/// A shader model version. s&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&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&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&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&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"
};
}