IR (intermediate representation) types for the Prism shader compiler editor: field, struct, varying, combo declaration, module metadata and the IrModule container with lists of structs, globals, helpers, functions, varyings and includes plus simple lookup helpers and AddInclude.
using Editor.Prism.Core;
namespace Editor.Prism.Compiler.Ir;
/// <summary>One field of an <see cref="IrStruct"/>.</summary>
public sealed record IrField( string Name, ShaderType Type, string Semantic, IrInterpolation Interpolation )
{
/// <summary>Declare a field with no semantic and default interpolation.</summary>
public IrField( string name, ShaderType type ) : this( name, type, null, IrInterpolation.Linear ) { }
/// <inheritdoc/>
public override string ToString() =>
string.IsNullOrEmpty( Semantic ) ? $"{Type.Hlsl} {Name}" : $"{Type.Hlsl} {Name} : {Semantic}";
}
/// <summary>A struct emitted into the module: vertex input, pixel input, varyings or a user type.</summary>
public sealed record IrStruct( string Name )
{
/// <summary>Fields, in declaration order.</summary>
public List<IrField> Fields { get; init; } = new();
/// <summary>Include directives emitted inside the struct body, as the engine's block format expects.</summary>
public List<string> Includes { get; init; } = new();
/// <inheritdoc/>
public override string ToString() => $"struct {Name} ({Fields.Count} fields)";
}
/// <summary>
/// An allocated vertex-to-pixel interpolator.
/// <para>
/// Semantic indices start at <see cref="PrismConstants.FirstFreeTexcoord"/>, because the engine's
/// <c>common/pixelinput.hlsl</c> already consumes TEXCOORD0-7 and TEXCOORD11. Overflow is a clear
/// error rather than a mysterious compile failure.
/// </para>
/// </summary>
public sealed record IrVarying( string Name, ShaderType Type, int Slot, string Semantic, IrInterpolation Interpolation )
{
/// <summary>The node whose value travels through this varying.</summary>
public NodeId Origin { get; init; }
/// <inheritdoc/>
public override string ToString() => $"{Type.Hlsl} {Name} : {Semantic}";
}
/// <summary>What kind of shader combo a keyword declares.</summary>
public enum ComboKind
{
/// <summary>A material feature, exposed in the material editor as <c>F_NAME</c>.</summary>
Feature,
/// <summary>A static combo, compiled into separate variants.</summary>
Static,
/// <summary>A dynamic combo, selected at draw time.</summary>
Dynamic
}
/// <summary>A combo declaration lowered from a graph keyword.</summary>
public sealed record ComboDecl( string Name, ComboKind Kind, IReadOnlyList<string> Values, int Default, string Group )
{
/// <inheritdoc/>
public override string ToString() => $"{Kind} {Name}";
}
/// <summary>
/// Module-level facts the backends need: what the shader is for, how it blends, which passes it
/// declares and which capabilities it ended up using.
/// </summary>
public sealed class ModuleMetadata
{
/// <summary>Base name of the generated artifacts.</summary>
public string Name { get; set; } = "prism_shader";
/// <summary>Description written into the shader header.</summary>
public string Description { get; set; }
/// <summary>Version written into the shader header.</summary>
public string Version { get; set; } = PrismConstants.EditorVersion;
/// <summary>What the graph is for.</summary>
public ShaderDomain Domain { get; set; } = ShaderDomain.Surface;
/// <summary>How a surface graph resolves to pixels.</summary>
public ShadingModel ShadingModel { get; set; } = ShadingModel.Lit;
/// <summary>Output blending.</summary>
public SurfaceBlendMode BlendMode { get; set; } = SurfaceBlendMode.Opaque;
/// <summary>Triangle culling.</summary>
public CullMode CullMode { get; set; } = CullMode.Back;
/// <summary>Declared render passes, e.g. <c>Forward</c>, <c>Depth</c>, <c>ToolsShadingComplexity</c>.</summary>
public List<string> Modes { get; } = new();
/// <summary>Combos lowered from graph keywords.</summary>
public List<ComboDecl> Combos { get; } = new();
/// <summary>Stages the module actually emits.</summary>
public StageMask Stages { get; set; } = StageMask.VertexPixel;
/// <summary>Capabilities the module ended up requiring.</summary>
public HashSet<Capability> Capabilities { get; } = new();
/// <summary>True when the second UV channel is used, which drives <c>#define S_UV2 1</c>.</summary>
public bool UsesUv2 { get; set; }
/// <summary>True when back faces are rendered.</summary>
public bool RenderBackfaces { get; set; }
/// <summary>True when temps get descriptive names and per-node comments are emitted.</summary>
public bool DebugSymbols { get; set; }
}
/// <summary>
/// The complete, backend-independent description of one shader. Everything upstream of this is
/// graph work; everything downstream is text generation. Both backends read exactly this.
/// </summary>
public sealed class IrModule
{
/// <summary>Vertex input, pixel input, varying and user structs.</summary>
public List<IrStruct> Structs { get; } = new();
/// <summary>Uniforms, textures, samplers and buffers, in declaration order and deduplicated.</summary>
public List<GlobalDecl> Globals { get; } = new();
/// <summary>Shared helper functions, topologically sorted by their requirements.</summary>
public List<HelperFunction> Helpers { get; } = new();
/// <summary>Entry points and generated functions.</summary>
public List<IrFunction> Functions { get; } = new();
/// <summary>Allocated vertex-to-pixel interpolators.</summary>
public List<IrVarying> Varyings { get; } = new();
/// <summary>Include directives the module needs, deduplicated, in insertion order.</summary>
public List<string> Includes { get; } = new();
/// <summary>Module-level facts.</summary>
public ModuleMetadata Meta { get; } = new();
/// <summary>Find a function by name.</summary>
public IrFunction FindFunction( string name ) =>
Functions.FirstOrDefault( x => x.Name == name );
/// <summary>The entry point for a stage, if the module emits one.</summary>
public IrFunction EntryPoint( ShaderStage stage ) =>
Functions.FirstOrDefault( x => x.IsEntryPoint && x.Stage == stage );
/// <summary>Find a global by name.</summary>
public GlobalDecl FindGlobal( string name ) =>
Globals.FirstOrDefault( x => x.Name == name );
/// <summary>Find a struct by name.</summary>
public IrStruct FindStruct( string name ) =>
Structs.FirstOrDefault( x => x.Name == name );
/// <summary>Add an include if it is not already present.</summary>
public void AddInclude( string path )
{
if ( string.IsNullOrWhiteSpace( path ) ) return;
if ( Includes.Contains( path ) ) return;
Includes.Add( path );
}
}