Editor/Prism/Compiler/EmitContext.cs

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.

ReflectionFile Access
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 );
}