DreamShaderLang
Language

Layout and

Pin generated node positions with Layout = { Node(...); Comment(...); } and group graph statements with

Layout pins generated node positions and declares comment boxes, replacing the automatic layout pass. #Region groups graph statements so the automatic pass has something to draw a box around. Neither changes shader math.

The two live in different places: Layout is a section next to Graph; #Region directives are written inside the Graph body.

Synopsis

Layout [=]
{
    Node( Var = "<variable>", X = <int>, Y = <int> ) ;
    Comment( Name = "<title>", X = <int>, Y = <int>, W = <int>, H = <int>
             [, Color = float4( <r>, <g>, <b>, <a> )] ) ;

}
NotationMeaningExample
<x>Placeholder — substitute a real value; the angle brackets are not typed.Name = <string>
[ x ]Optional — the whole group may be left out.[, Root = <string>]
{ a | b }Choice — take exactly one of the alternatives separated by |.{ Node( … ) | Comment( … ) }
Repetition — the preceding item may appear any number of times.<property-declaration> …

Statements are ;-separated calls. Statement names and argument keys are matched case-insensitively, and the = before the block is optional since 1.5.0. Layout is accepted in Shader, ShaderFunction, ShaderLayer and ShaderLayerBlend, and is not accepted in a VirtualFunction, which generates no graph.

Every argument is a Key = Value pair. The positional form Node("Tint", X=0, Y=0) is not accepted and fails with Layout argument '{Argument}' must use Key=Value syntax. The name argument is Var on Node and Name on Comment.

Node

Pins one already-generated expression to an exact position.

ArgumentRequiredType
Varyestext
Xyesinteger
Yyesinteger

Var names a value recorded during graph construction. Three kinds of name are recorded:

Name kindRecorded when
a Graph statement's target variablethe statement produced a node — declared locals and assigned output variables alike
a Properties declaration namethe graph actually read that property; property nodes are created lazily
an Inputs / Outputs declaration nameonly in ShaderFunction, ShaderLayer and ShaderLayerBlend, where each becomes a FunctionInput / FunctionOutput node

Both MaterialExpressionEditorX/Y and the editor graph node's NodePosX/Y are written, so the position survives reopening the material.

A Var that matches nothing is silently ignored — there is no diagnostic for a typo, or for pinning a property the graph never reads.

Comment

Creates a comment box at an exact rectangle.

ArgumentRequiredTypeEffect
NameyestextBox title. Emitted as DreamShader: <Name> — see Notes.
XyesintegerLeft edge.
YyesintegerTop edge.
WyesintegerWidth, clamped to a minimum of 120.
HyesintegerHeight, clamped to a minimum of 80.
Colornofloat4 literalBox colour. Defaults to float4(0.10, 0.16, 0.22, 0.35).

W and H carry struct defaults but are nonetheless required arguments. Comment(Name="X", X=0, Y=0) fails with Layout argument 'W' must be an integer. — a missing integer argument and a malformed one report the same message.

The Color literal follows the general vector-literal grammar: the token before ( is ignored, so float4(…), vec4(…) and (…) all parse. One component splats to (x, x, x, 1), two give (x, y, 0, 0), three give (x, y, z, 1), and components past the fourth are parsed and discarded.

Generated comment boxes always use FontSize = 24 and group mode, so dragging the box moves the nodes it encloses.

Argument parsing

RuleDetail
Statement shape<Name>( <key> = <value> [, <key> = <value> ]… ) — text after the closing ) is an error
Statement namemust be a valid identifier, matched case-insensitively against Node and Comment
Key normalisationtrimmed, then lower-cased
Value handlingone surrounding "…" pair is stripped and unescaped, then the value is trimmed
Duplicate keyrejected with a diagnostic — unlike Settings, where the later key wins
Empty key or valuerejected
Commentsstripped from the section body before statements are split

Coordinate space

Positions are Unreal material-graph editor coordinates: X increases to the right, Y increases downward. Negative X is "upstream"; the material root node sits to the right of everything else.

The constants the automatic layout pass uses make a useful frame of reference when placing nodes by hand:

LandmarkX
generated property / parameter nodes-800
FunctionInput nodes-800
inline literal constants-1120
one automatic layout column420 wide, laid out leftwards from the output column
output-binding reroute usages720
FunctionOutput nodes900
the automatic layout's output column900
Expression( … ).Pin[i] output-target nodes1200

Vertical stride is 220 for property nodes and automatic layout rows, 180 for function inputs and outputs.

Explicit versus automatic layout

A Layout block does not merely add to the automatic pass — it replaces the ranking algorithm.

ConditionResult
at least one Node matched a recorded variable, or at least one Comment is declaredexplicit layout runs
a Layout block exists but no Node matched and no Comment is declaredthe automatic layout pass runs as if the block were absent
no Layout blockthe automatic layout pass runs

On the explicit path, expressions the block did not name are still placed: positions propagate iteratively from already-positioned neighbours — midway between a known consumer and a known dependency, 360 left of a known consumer, or 360 right of a known dependency — with a collision fan-out for coincident slots. Anything still unplaced goes into a fallback column to the left of everything positioned.

Layout is skipped entirely in transient (in-memory) mode. Auto-compile-on-save, the Gen page buttons and the live preview all generate in memory, so a Layout block has no visible effect there — the nodes keep whatever positions the construction pass produced. Positions appear only in a persisted asset: at cook, through the commandlet, or after an explicit Materialize. See In-memory Materials.

A second Layout section resets the first rather than appending. Only the last Layout block in a body has any effect. Every other section in the language either appends or merges.

#Region / #EndRegion

Region directives live in Graph body text, not in Layout. They name a span of graph statements; the layout pass turns each distinct region name into a comment-box block.

Graph = {
    #Region "Surface"
    Color = BaseColor.rgb;
    Rough = Roughness;
    #EndRegion

    #Region "Emissive"
    Glow = Tint * Intensity;
    #EndRegion
}
RuleDetail
Recognitionthe trimmed line must start with #Region / #EndRegion, matched case-insensitively, followed by end of line, whitespace, or "
Namethe rest of the line, unquoted and trimmed; required on #Region
Nestingregions nest — the parser keeps a stack
SpanStartLine is the line after #Region; EndLine is the line before #EndRegion, floored at StartLine
Line numberingdirective lines are replaced by an equal-length run of spaces, so diagnostics keep their real line and column

A statement inside a region tags the variable it produces with the region name. On the automatic layout path each region becomes one comment box; on the explicit path region names contribute the block boundaries used to decide where cross-block reroutes are inserted.

#Region names and Layout Comment names are independent. Declaring a Comment whose rectangle happens to contain a region's nodes does not merge the two — on the explicit path, geometric containment is what assigns a node to a comment block.

There is no other # directive. #Region is recognised only inside a Graph body; a # anywhere else is not a directive.

Notes

  • Comment text is always prefixed with DreamShader: . Comment(Name="Sampling", …) produces a box reading DreamShader: Sampling. That prefix is also the teardown marker: on regeneration, comment boxes whose text starts with DreamShader: are deleted and rebuilt, and boxes that do not carry the prefix survive. A hand-authored comment box is the only hand edit that survives a regeneration. See Regeneration.
  • A Comment whose Name is empty or whitespace after unquoting is rejected at parse time, so no box is ever created for one.
  • The decompiler emits Layout blocks in exactly this format, so a material can be exported, edited and regenerated with its positions intact. Emission is controlled by the Export Decompiled Layout project setting, on by default.
  • The automatic pass gives up on very large graphs. At or above 1200 expressions it logs Skipping automatic layout for large DreamShader graph ({Count} nodes). Existing generated positions will be used. and leaves construction-time positions in place. A Layout block is the way to control those graphs.

Example

Shader(Name="Materials/M_Layout", Root="Game")
{
    Properties {
        VectorParameter Tint      = float4(0.4, 0.8, 1.0, 1.0);
        ScalarParameter Intensity = 2.0 [Slider(0, 10)];
    }

    Outputs {
        float3 Color;
        Base.EmissiveColor = Color;
    }

    Graph {
        #Region "Emissive"
        float3 Boosted = Tint.rgb * Intensity;
        Color = Boosted + vec3(0.05, 0.05, 0.05);
        #EndRegion
    }

    Layout {
        Comment(Name="Emissive", X=-1300, Y=-260, W=1100, H=520,
                Color=float4(0.10, 0.22, 0.16, 0.35));
        Node(Var="Tint",      X=-1200, Y=-160);
        Node(Var="Intensity", X=-1200, Y=  60);
        Node(Var="Boosted",   X= -800, Y=-160);
        Node(Var="Color",     X= -400, Y= -60);
    }
}

Generated graph:

Comment      "DreamShader: Emissive"   at (-1300, -260)  size 1100 x 520
Tint         VectorParameter           at (-1200, -160)
Intensity    ScalarParameter           at (-1200,   60)
Boosted      Multiply                  at ( -800, -160)
Color        Add                       at ( -400,  -60)
DS_Color_<n> NamedReroute              positioned by propagation

Nothing reaches disk unless the material is persisted — see the transient-mode warning above.

Diagnostics

MessageCauseFix
Invalid Layout statement '{Statement}'.No balanced ( … ).
Unexpected text after Layout statement '{Statement}'.Trailing text after the closing ).
Invalid Layout statement name in '{Statement}'.The text before ( is not an identifier.
Unknown Layout statement '{Name}'.A call name other than Node or Comment.
Layout argument '{Argument}' must use Key=Value syntax.A positional argument — the old Node("Tint", …) form reaches this.Write Node(Var="Tint", X=…, Y=…).
Invalid Layout argument '{Argument}'.An empty key or an empty value.
Layout argument '{Key}' is declared more than once.A duplicate argument key. Unlike Settings, the later one does not win.
Layout argument '{Name}' is required.A required text argument is missing or blank — Var on Node, Name on Comment.
Layout argument '{Name}' must be an integer.A required integer argument is missing or not an integer — W and H on Comment reach this most often.
Layout Comment Color must be a float4 literal in '{Statement}'.Color is not a vector literal.
Graph #Region on line {Line} must include a name.#Region with nothing after it.
Graph #EndRegion on line {Line} has no matching #Region.An unbalanced #EndRegion.
Graph #Region '{Name}' is missing #EndRegion.A region still open at the end of the Graph body.

Where to next

On this page