DreamShaderLang
The Graph Language

What Graph Is Not

Loops, return, ternary, %, comparisons and indexing — what each one actually does in a Graph block, including the cases that fail silently, and what to write instead.

A Graph block is a node-graph builder, not a shader compiler. It has ten statement forms, four arithmetic operators and no control flow other than if. Everything else either fails with a message that names a variable type rather than the construct — or, for a large and important set of cases, compiles silently into something smaller than what was written.

If you are arriving from HLSL or GLSL, this is the page to read first.

Two mechanisms explain everything below

1. The statement classifier turns unknown syntax into a declaration. There is no keyword table and no "unsupported keyword" diagnostic. A statement with no top-level = is split at its last top-level whitespace into a type token and a name, and that split succeeds for almost any text. So return Color, while (t) {} and struct S { float a; } all become declarations of an unresolvable type. Parenthesis depth and string literals are tracked during the split — brace depth is not.

2. The tokenizer maps every unknown character to end-of-input. The expression parser accepts an expression followed by end-of-input, so anything after the first unknown character is discarded without a diagnostic.

Silently truncated expressions

The tokenizer knows only letters, digits, _, ., ", whitespace, the punctuation ( ) , + - * / = and the two-character ::. Every other character becomes the end-of-input token%, !, <, >, &, |, ^, ~, ?, [, ], {, }, ;, #, @, $, ', the backtick, a lone :, and anything else. The parser then accepts the expression that precedes it and throws the rest away with no message at all.

WrittenActually compiledWrite instead
a % bafmod(a, b) or mod(a, b)
a && banested if statements
a || banested if statements, or max on 0/1 masks
a & b, a | b, a ^ baa Function with an HLSL body
a << 2, a >> 2aa * 4.0, a / 4.0, or a Function
a < b, a > b, a <= b, a >= b, a != baan if condition
a ? b : caif / else, lerp(c, b, mask), or a StaticSwitchParameter call
v[0]va swizzle: v.x
~a (trailing)aa Function with an HLSL body

Modulo

// Written — the modulo is discarded, Wave is just Time.
float Wave = UE.Time() % 1.0;

// Actually compiled:
float Wave = UE.Time();

// Correct:
float Wave = fmod(UE.Time(), 1.0);

Compound conditions

// Written — the green test is discarded.
if (Color.r > 0.5 && Color.g > 0.5) { Out = One; } else { Out = Zero; }

// Actually compiled:
if (Color.r > 0.5) { Out = One; } else { Out = Zero; }

// Correct:
if (Color.r > 0.5) {
    if (Color.g > 0.5) { Out = One; } else { Out = Zero; }
} else {
    Out = Zero;
}

Making truncation visible

Two positions do report the problem, and both are worth exploiting.

PositionMessage
Leading — the unknown character starts the expression, as in !x or ~xUnexpected token '{Char}' in Graph expression.
Inside parentheses or an argument list — the expected ) or , is never found, as in (a % b) or f(a % b)Expected token type {Code} in Graph expression near '{Text}'.

Wrapping a suspect expression in parentheses is the reliable way to force a diagnostic: float x = a % b; compiles silently, float x = (a % b); errors. When a generated material looks like it lost half its logic, parenthesise the suspect lines and rebuild.

Compound assignment and increment

+= -= *= /= ++ -- are not operators. What happens depends on whitespace, because the statement is split at its first top-level = and the left side is then tested for a type/name split.

WrittenResult
a += b;Unsupported Graph variable type 'a' for '+'.
a -= b;Unsupported Graph variable type 'a' for '-'.
a *= b;Unsupported Graph variable type 'a' for '*'.
a /= b;Unsupported Graph variable type 'a' for '/'.
a++;Graph expression statements currently support only Function calls with explicit out arguments.
a--;the same message

The spaceless forms compile with no diagnostic and no effect. a+=b; has no whitespace on the left of the =, so it is not a declaration — it is a plain assignment creating a brand-new Graph variable literally named a+. a is unchanged and nothing warns.

// Written — silently creates a variable named 'a+'; 'Sum' never changes.
Sum+=Tint;

// Correct:
Sum = Sum + Tint;

The same applies to a-=b, a*=b and a/=b.

Prefix ++ and -- parse as repeated unary operators and do nothing. ++a is +(+a), the identity, and emits no node. --a is -(-a), which emits two Multiply nodes by -1 and is also numerically a. Neither increments anything.

// Written — Counter is unchanged, and two Multiply nodes are generated.
float Next = --Counter;

// Correct:
float Next = Counter - 1.0;

There is no storage to increment in the first place — a Graph variable is a binding to a node and a pin, not a memory cell.

Statement-level constructs

None of these are keywords. Each is classified as a declaration, and the reported "variable type" is whatever text preceded the last top-level whitespace.

WrittenMessageWrite instead
return Color;Failed to declare Graph variable 'Color'. Unsupported Graph variable type 'return'.assign the declared output variable: Color = …;
return;Graph expression statements currently support only Function calls with explicit out arguments.as above
for (int i = 0; i < 3; i = i + 1) {}Failed to declare Graph variable '{}'. Unsupported Graph variable type 'for (int i = 0; i < 3; i = i + 1)'.unroll by hand, or move the loop into a Function whose body is real HLSL
while (t) {}Failed to declare Graph variable '{}'. Unsupported Graph variable type 'while (t)'.as above
do {} while (t);Failed to declare Graph variable '(t)'. Unsupported Graph variable type 'do {} while'.as above
switch (Mode) {}Failed to declare Graph variable '{}'. Unsupported Graph variable type 'switch (Mode)'.nested if statements, or a StaticSwitchParameter call
break;Graph expression statements currently support only Function calls with explicit out arguments.
continue;the same message
float Foo(float x) { return x; }Failed to declare Graph variable '}'. Unsupported Graph variable type 'float Foo(float x) { return x;'.declare it at top level as a Function or GraphFunction and call it
struct S { float a; }Failed to declare Graph variable '}'. Unsupported Graph variable type 'struct S { float a;'.use MaterialAttributes, or a struct inside a Function body
#define K 2Failed to declare Graph variable '2'. Unsupported Graph variable type '#define K'.a const property, or a preprocessor directive inside a Function body
#include "X.ush"Failed to declare Graph variable '"X.ush"'. Unsupported Graph variable type '#include'.import at file level, or a Function that uses the generated include

A braced construct carries no top-level ;, so the statement splitter does not stop at its closing } — it keeps scanning to the next top-level ;. A loop followed by more code therefore swallows that code into the same statement, and the quoted type and name in the message contain unrelated text. Given

for (int i = 0; i < 3; i = i + 1) { Sum = Sum + 1.0; }
Color = Sum;

the whole thing is one statement, split at the = of Color, and the message is Unsupported Graph variable type 'for (int i = 0; i < 3; i = i + 1) { Sum = Sum + 1.0; }' for 'Color'. The messages in the table above assume the construct stands alone.

Wrong-case keywords

if and else are the only keywords in the Graph language, and both are matched case-sensitively. A mis-cased spelling is not a keyword and falls into the declaration classifier.

WrittenResult
If (x) {}Failed to declare Graph variable '{}'. Unsupported Graph variable type 'If (x)'.
IF (x) {}the same shape
if (x) {} Else {}the if parses; Else {} becomes a separate statement and reports Failed to declare Graph variable '{}'. Unsupported Graph variable type 'Else'.
if (x) {} ELSE {}the same shape

Everything else in the language is case-insensitive — type tokens, constructor names, builtins, true/false, swizzle channels, argument names. The one other case-sensitive name is SampleTexture2D.

Expression-level constructs that do error

These fail loudly, which makes them the easy ones.

WrittenMessage
!xUnexpected token '!' in Graph expression.
~xUnexpected token '~' in Graph expression.
x = a == b;Unexpected token '=' in Graph expression.= is a real token, so == is not truncated
a == b; (as a statement)In Graph statement 'a == b': Unexpected token '=' in Graph expression.
x = a++;Unexpected token '' in Graph expression. — the postfix + demands an operand and finds end-of-input
x = (float)a;Unexpected token 'a' in Graph expression. — there are no C-style casts; write float(a)
x = 0x10;Unexpected token 'x10' in Graph expression. — there are no hex, octal or binary literals
x = 1.0fx;Unexpected token 'fx' in Graph expression. — a numeric suffix is consumed only at an identifier boundary
x = "text";String literals can only be used in named UE builtin arguments.
float4 v = {{1,2},{3,4}};Invalid brace initializer for type 'float4'. Unexpected token '{' in Graph expression. — brace initializers do not nest
f(a b)Expected token type {Code} in Graph expression near '{Text}'. — a missing ,
if (a) Color = X;Graph if statement is missing a '{ ... }' body. — braces are mandatory

The empty quotes in Unexpected token '' in Graph expression. are not a formatting fault. The reported token is the end-of-input token, whose text is empty.

Features that simply do not exist

FeatureStatusAlternative
Matrix types (float3x3, float4x4, mat2, mat3, mat4)absent from the type, constructor and function-signature sets; a matrix-typed Function parameter or result is rejected at the call site with uses unsupported type '{Type}'UE.TransformVector / UE.TransformPosition, or matrix locals inside a Function body
Arrays and indexingabsentswizzles for channels; Texture2DArray sampling for layers
inout parametersabsent — only in and outpass an in and an out
Function declarations inside Graphabsenttop-level Function / GraphFunction
Integer arithmeticabsent — int, uint, bool and half collapse to float widthsthe integer marker exists only to reject int(a) / int(b)
Hex, octal and binary literalsabsentdecimal literals
String valuesabsent outside named UE.* arguments
Ternary conditionalabsentif / else, lerp, StaticSwitchParameter
Comma operatorabsentseparate statements
Assignment inside an expressionabsentseparate statements
Nested brace initializersabsenta constructor call: float4(float2(1,2), float2(3,4))
Preprocessor directivesabsentimport, const properties, #Region for layout only
Code = { … } inside a Shaderremoved — hard error Shader graph sections now use Graph = { ... }. Function Code = { ... } is still supported.Graph = { … }

Worked example

A GLSL-shaped attempt, and what it actually builds:

// Does not do what it looks like.
Graph {
    vec2  uv    = UE.TexCoord(Index = 0);
    float t     = UE.Time() % 4.0;                 // silently just UE.Time()
    float mask  = uv.x > 0.5 && uv.y > 0.5;        // silently just uv.x
    vec3  col   = mask ? Hot : Cold;               // silently just mask
    col        *= Gain;                            // error: type 'col' for '*'
    Color = col;
}

Only the last line errors. The three lines above it compile, and the material is wrong.

// Correct.
Graph {
    vec2  uv = UE.TexCoord(Index = 0);
    float t  = fmod(UE.Time(), 4.0);

    vec3 col;
    if (uv.x > 0.5) {
        if (uv.y > 0.5) { col = Hot; } else { col = Cold; }
    } else {
        col = Cold;
    }

    Color = col * Gain;
}

When to reach for a Function

Anything genuinely imperative — a loop, a bitwise mask, a switch — belongs in a Function, whose body is real HLSL and is compiled into a Custom node:

Function SelfContained float Ring(in float2 uv, in float count)
{
    float acc = 0.0;
    for (int i = 0; i < 4; ++i)
    {
        acc += sin(uv.x * count * (i + 1));
    }
    return acc * 0.25;
}
Graph {
    float r = Ring(uv, 8.0);
}

If the helper needs to read graph nodes as well, declare it as a GraphFunction instead — its body is still HLSL, but UE.* calls inside it are hoisted into Custom-node input pins. See Functions.

See also

On this page