Compiler EmitContext API for editor-side Prism shader nodes. Defines an abstract contract used by nodes to read inputs, produce IR values, construct expressions, control flow, declare globals/helpers, emit diagnostics and stage-specific behavior.
using Editor.Prism.Compiler.Backends;
using Editor.Prism.Compiler.Ir;
using Editor.Prism.Core;
using Editor.Prism.Model;
namespace Editor.Prism.Compiler;
/// <summary>
/// The node-facing compiler API — the most important contract in the codebase.
/// <para>
/// Nodes never see HLSL or Slang text. They read their inputs, build IR, and write their outputs.
/// Everything a node can legally do during emission is on this class; anything not expressible here
/// is either a <see cref="HelperFunction"/> (a body per backend, deduplicated per module) or a bug.
/// </para>
/// <para>
/// This is an abstract class rather than a partial one on purpose: the contract is frozen here and
/// the implementation lives in a separate work package as a subclass. Nodes only ever see this type.
/// </para>
/// </summary>
public abstract class EmitContext
{
// ---- context ---------------------------------------------------------
/// <summary>The node currently emitting.</summary>
public abstract PrismNode Node { get; }
/// <summary>The document being compiled.</summary>
public abstract IPrismGraph Graph { get; }
/// <summary>The stage this emission lands in. A node used in both stages is emitted twice.</summary>
public abstract ShaderStage Stage { get; }
/// <summary>What the compile is for.</summary>
public abstract CompileMode Mode { get; }
/// <summary>Where problems go. Diagnostics reported here are automatically attributed to <see cref="Node"/>.</summary>
public abstract DiagnosticSink Diagnostics { get; }
/// <summary>
/// The primary target backend. Branching on <c>Backend.Id</c> is discouraged and lint-flagged —
/// prefer a <see cref="HelperFunction"/> carrying one body per backend. May be null when the
/// module is being built for more than one target.
/// </summary>
public abstract IShaderBackend Backend { get; }
// ---- reading ---------------------------------------------------------
/// <summary>
/// Read an input. Reports a missing-input error and returns <see cref="IrValue.Invalid"/> when
/// nothing is connected and no inline value exists.
/// </summary>
public virtual IrValue In( string port )
{
if ( TryIn( port, out var value ) ) return value;
Error( $"Input '{port}' is required", port );
return IrValue.Invalid;
}
/// <summary>Read an input, falling back to a value when nothing is connected.</summary>
public virtual IrValue In( string port, IrValue fallback ) =>
TryIn( port, out var value ) ? value : fallback;
/// <summary>Read an input without reporting anything when it is absent.</summary>
public abstract bool TryIn( string port, out IrValue value );
/// <summary>Read an input and coerce it to a type, warning on a lossy conversion.</summary>
public abstract IrValue InAs( string port, ShaderType type );
/// <summary>
/// Read two inputs and promote both to their common type, so a node never has to think about
/// <c>float</c> plus <c>float3</c>.
/// </summary>
public virtual (IrValue A, IrValue B) InPair( string a, string b )
{
var left = In( a );
var right = In( b );
if ( !left.IsValid || !right.IsValid ) return (left, right);
var promoted = TypeRules.Promote( left.Type, right.Type );
if ( promoted.IsVoid ) return (left, right);
return (Cast( left, promoted ), Cast( right, promoted ));
}
/// <summary>Read one of the node's serialized properties.</summary>
public abstract T Prop<T>( string name );
// ---- writing ---------------------------------------------------------
/// <summary>Publish the value of one of the node's outputs.</summary>
public abstract void Out( string port, IrValue value );
// ---- constants -------------------------------------------------------
/// <summary>A float literal.</summary>
public abstract IrValue Const( float v );
/// <summary>A float2 literal.</summary>
public abstract IrValue Const( Vector2 v );
/// <summary>A float3 literal.</summary>
public abstract IrValue Const( Vector3 v );
/// <summary>A float4 literal.</summary>
public abstract IrValue Const( Vector4 v );
/// <summary>A colour literal.</summary>
public abstract IrValue Const( Color v );
/// <summary>A bool literal.</summary>
public abstract IrValue Const( bool v );
/// <summary>An int literal.</summary>
public abstract IrValue Const( int v );
/// <summary>A literal of an arbitrary type.</summary>
public abstract IrValue Const( ShaderType type, ConstValue value );
// ---- building --------------------------------------------------------
/// <summary>Call a canonical intrinsic. The backend spells it.</summary>
public abstract IrValue Call( Intrinsic id, params IrValue[] args );
/// <summary>Apply a binary operator, promoting the operands.</summary>
public abstract IrValue Bin( BinaryOp op, IrValue a, IrValue b );
/// <summary>Apply a unary operator.</summary>
public abstract IrValue Un( UnaryOp op, IrValue a );
/// <summary>Swizzle a value, e.g. <c>"xy"</c>, <c>"rgb"</c>, <c>"xxxw"</c>.</summary>
public abstract IrValue Swizzle( IrValue v, string mask );
/// <summary>Build a vector, matrix or struct from parts.</summary>
public abstract IrValue Construct( ShaderType t, params IrValue[] parts );
/// <summary>Convert a value to a type. A conversion to the same type is a no-op.</summary>
public abstract IrValue Cast( IrValue v, ShaderType t );
/// <summary>Component-wise select. Always lowered to <c>select()</c>, never to <c>?:</c> on a vector.</summary>
public abstract IrValue Select( IrValue cond, IrValue a, IrValue b );
/// <summary>Force a value into a named temp, so it is computed once and reads well in the output.</summary>
public abstract IrValue Let( string hint, IrValue value );
// ---- real control flow -----------------------------------------------
/// <summary>Open an <c>if</c> block. Dispose to close it.</summary>
public abstract IDisposable If( IrValue cond );
/// <summary>Open the <c>else</c> block of the most recently closed <c>if</c>. Dispose to close it.</summary>
public abstract IDisposable Else();
/// <summary>Open a counted loop over <c>[0, count)</c>. Dispose to close it.</summary>
public abstract IDisposable For( string var, IrValue count, out IrValue index );
/// <summary>Leave the innermost loop.</summary>
public abstract void Break();
/// <summary>Skip to the next iteration of the innermost loop.</summary>
public abstract void Continue();
/// <summary>Kill the fragment when the condition is true. Pixel stage only.</summary>
public abstract void Discard( IrValue condition );
// ---- shared resources ------------------------------------------------
/// <summary>Call a shared helper function, emitting it into the module once. Deduplicated by name.</summary>
public abstract IrValue Helper( HelperFunction fn, params IrValue[] args );
/// <summary>Declare or reuse a module-level uniform, texture, sampler or buffer.</summary>
public abstract IrValue Global( GlobalDecl decl );
/// <summary>Read an environment-provided value. Lowered per stage by the backend.</summary>
public abstract IrValue Builtin( Builtin id );
/// <summary>
/// Pass a vertex-stage value to the pixel stage through an automatically allocated interpolator.
/// Reading it from the vertex stage returns the value unchanged.
/// </summary>
public abstract IrValue Varying( string name, IrValue vsValue );
/// <summary>Add an include to the module.</summary>
public abstract void Include( string path );
/// <summary>Declare that this node needs a capability. Errors when the target cannot provide it.</summary>
public abstract void Require( Capability c );
/// <summary>Report an error against this node, optionally against one of its ports.</summary>
public abstract void Error( string message, string port = null );
/// <summary>Report a warning against this node, optionally against one of its ports.</summary>
public abstract void Warn( string message, string port = null );
/// <summary>Report an informational note against this node, optionally against one of its ports.</summary>
public abstract void Info( string message, string port = null );
}