Data model for compile outputs used by the editor. Defines preview uniform/texture records (PreviewAttribute, PreviewTexture), compile statistics (CompileStats), and the CompileResult record that holds artifacts, diagnostics, stats, preview data and helper accessors.
using Editor.Prism.Compiler.Backends;
using Editor.Prism.Compiler.Ir;
using Editor.Prism.Core;
namespace Editor.Prism.Compiler;
/// <summary>
/// A uniform the preview can push straight to the GPU. In <see cref="CompileMode.Preview"/> every
/// literal and every graph parameter becomes one of these, which is why dragging a slider costs zero
/// compiles. Pushed with a dictionary indexer, never <c>Dictionary.Add</c> — the built-in editor's
/// attribute helper throws on a duplicate name.
/// </summary>
public sealed record PreviewAttribute( string Name, ShaderType Type, ConstValue Value )
{
/// <summary>The node whose literal this is, when it came from one.</summary>
public NodeId Node { get; init; }
/// <summary>The port whose literal this is, when it came from one.</summary>
public PortId Port { get; init; }
/// <summary>The blackboard parameter this came from, when it came from one.</summary>
public ParamId Parameter { get; init; }
/// <inheritdoc/>
public override string ToString() => $"{Type.Hlsl} {Name} = {Value}";
}
/// <summary>
/// A texture slot the preview has to fill itself, because nothing bakes it for a shader rendered
/// without a material. See <c>NodeEmitter.PreviewTextureBinding</c> for why this exists.
/// </summary>
/// <param name="Name">The render-attribute name the shader binds the slot to.</param>
/// <param name="Asset">Path of the source image the graph asked for.</param>
/// <param name="Srgb">True when the slot holds sRGB-encoded colour rather than linear data.</param>
public sealed record PreviewTexture( string Name, string Asset, bool Srgb )
{
/// <summary>The blackboard parameter behind the slot, when it came from one.</summary>
public ParamId Parameter { get; init; }
/// <inheritdoc/>
public override string ToString() => $"{Name} = \"{Asset}\"{( Srgb ? " (srgb)" : string.Empty )}";
}
/// <summary>Counters for the status bar and for spotting performance regressions between builds.</summary>
public sealed record CompileStats
{
/// <summary>Nodes visited during emission.</summary>
public int NodeCount { get; init; }
/// <summary>Statements in the emitted module.</summary>
public int StatementCount { get; init; }
/// <summary>Temps bound by the emitter after CSE.</summary>
public int TempCount { get; init; }
/// <summary>Expressions removed by CSE, folding and dead-code elimination.</summary>
public int OptimizedAway { get; init; }
/// <summary>Module-level declarations emitted.</summary>
public int GlobalCount { get; init; }
/// <summary>Interpolators allocated.</summary>
public int VaryingCount { get; init; }
/// <summary>Helper functions emitted.</summary>
public int HelperCount { get; init; }
/// <summary>Milliseconds spent in validation, solving and stage planning.</summary>
public double AnalysisMs { get; init; }
/// <summary>Milliseconds spent building and optimising the IR.</summary>
public double EmitMs { get; init; }
/// <summary>Milliseconds spent in the backends.</summary>
public double BackendMs { get; init; }
/// <summary>Total wall time of the compile.</summary>
public double TotalMs { get; init; }
/// <inheritdoc/>
public override string ToString() =>
$"{NodeCount} nodes, {StatementCount} statements, {TotalMs:0} ms";
}
/// <summary>
/// The result of a compile: one artifact per requested backend, everything that went wrong, the
/// uniforms the preview can push live, and the counters for the status bar.
/// </summary>
public sealed record CompileResult(
bool Ok,
IReadOnlyDictionary<string, BackendEmitResult> Artifacts,
IReadOnlyList<Diagnostic> Diagnostics,
IReadOnlyList<PreviewAttribute> PreviewAttributes,
CompileStats Stats )
{
/// <summary>The IR the artifacts were generated from. Kept so the code panel can print it.</summary>
public IrModule Module { get; init; }
/// <summary>
/// Texture slots the preview must push itself. Empty for every mode but
/// <see cref="CompileMode.Preview"/>, where a shipping <c>CreateInputTexture2D</c> slot would never
/// be filled because nothing compiles a material for the preview.
/// </summary>
public IReadOnlyList<PreviewTexture> PreviewTextures { get; init; } = Array.Empty<PreviewTexture>();
/// <summary>The request this result answers.</summary>
public CompileRequest Request { get; init; }
/// <summary>Number of errors reported.</summary>
public int ErrorCount => Diagnostics?.Count( x => x.Severity == DiagnosticSeverity.Error ) ?? 0;
/// <summary>Number of warnings reported.</summary>
public int WarningCount => Diagnostics?.Count( x => x.Severity == DiagnosticSeverity.Warning ) ?? 0;
/// <summary>The artifact produced by a backend, or null when that backend was not requested.</summary>
public BackendEmitResult Artifact( string backendId ) =>
Artifacts is not null && Artifacts.TryGetValue( backendId, out var result ) ? result : null;
/// <summary>The generated <c>.shader</c> text, when the s&box backend ran.</summary>
public string ShaderText => Artifact( PrismConstants.BackendHlsl )?.Text;
/// <summary>The generated <c>.slang</c> text, when the Slang backend ran.</summary>
public string SlangText => Artifact( PrismConstants.BackendSlang )?.Text;
/// <summary>A failed result carrying only diagnostics.</summary>
public static CompileResult Failed( IReadOnlyList<Diagnostic> diagnostics, CompileRequest request = null ) =>
new( false, new Dictionary<string, BackendEmitResult>(), diagnostics ?? Array.Empty<Diagnostic>(),
Array.Empty<PreviewAttribute>(), new CompileStats() )
{
Request = request
};
/// <inheritdoc/>
public override string ToString() =>
$"{( Ok ? "ok" : "failed" )}: {ErrorCount} errors, {WarningCount} warnings, {Stats}";
}