DreamShaderLang
The Graph Language

Calls

Calling Function, GraphFunction, ShaderFunction, VirtualFunction and parameter pins from a Graph block — value form, statement form, arguments and output selection.

Anything in a Graph body written as Name(...) is a call. Six declaration kinds and two kinds of parameter are callable, plus the builtins and constructors that are resolved first. Which form a call may take, whether it can be used as a value, and whether it accepts named arguments all depend on the kind of thing being called.

Synopsis

// value form — the call produces a value
<target> = <callee> ( [ <argument> [ , <argument> ] … ] ) ;

// statement form — the call writes into named out variables
<callee> ( [ <input-argument> , ] … <out-target> [ , <out-target> ] … ) ;

<callee>     := <identifier> | <namespace> :: <identifier>
<argument>   := <expression> | <identifier> = <expression> | default
<out-target> := <identifier>

Callable kinds

KindValue formStatement formNamed argumentsNode produced
Functiononly with exactly one declared outputyesnoCustom
GraphFunctiononly with exactly one declared outputyesnoCustom
ShaderFunctionyes, any output countyesyes, value form onlyMaterialFunctionCall
ShaderLayeryes, any output countyesyes, value form onlyMaterialFunctionCall
ShaderLayerBlendyes, any output countyesyes, value form onlyMaterialFunctionCall
VirtualFunctionyes, any output countyesyes, value form onlyMaterialFunctionCall
StaticSwitchParameter propertyyesnoyesStaticSwitchParameter
input-bearing parameteryesnoonly namedthe parameter's own node, configured

The current site once said return-style calls were unsupported. They are not: a single-output Function or GraphFunction is value-callable since 1.3.1, and every ShaderFunction-family and VirtualFunction call always has been.

UE.*, Substrate.*, math builtins, constructors and SampleTexture2D also use call syntax, but they are resolved before any of the kinds above. See UE.* Nodes and Math Builtins.

Dispatch order

A call name is probed strictly in this order. The first surface that claims the name wins; later surfaces are never consulted.

OrderSurfaceCase
1vector/scalar constructor name — the 34 spellingsinsensitive
2UE.SceneTextureinsensitive
3any name beginning with UE.insensitive
4any name beginning with Substrate. since UE 5.4insensitive
5math builtin — the 19 spellingsinsensitive
6SampleTexture2Dsensitive
7a declared property whose node type is StaticSwitchParameterinsensitive
8a declared property whose node type owns input pinsinsensitive
9Function, GraphFunction, ShaderFunction, ShaderLayer, ShaderLayerBlend, VirtualFunctioninsensitive
10fallback Function lookup — otherwise Unknown Graph function '{Name}'.insensitive

Step 5 has one subtlety: the math-builtin handler distinguishes "this is not a math builtin" from "this is one and the call is malformed". Only the second aborts, so clamp(x) reports Math function 'clamp' expects exactly 3 arguments. rather than falling through to a user function.

At step 9, if more than one declaration kind matches, the call fails with Graph call '{Name}' is ambiguous because multiple definitions use that name: {Kinds}. When exactly one matched, dispatch precedence is material function → VirtualFunctionGraphFunction, with Function reached through step 10.

Shadowing is silent and always runs one way — an earlier surface hides a later one. A Function named lerp, dot, saturate, float3, vec2 or any other builtin or constructor spelling parses fine, generates its HLSL helper, and is never invoked. Rename it. SampleTexture2D is the one case-sensitive comparison in the ladder, so a Function named sampletexture2d is reachable.

Callee spellings

SpellingResolves to
Namea Function / GraphFunction whose declared name matches, or a ShaderFunction-family / VirtualFunction block whose declared name matches
Namespace::Namea Function / GraphFunction declared inside Namespace(Name="Namespace"). Namespaced functions are reachable only by their fully qualified name — there is no using and no unqualified fallback.
DreamShaderFn_Namethe same Function — its generated HLSL symbol name is accepted as an alias
trailing path segmentfor a block declared Name="Functions/F_Tint", the segment after the final /F_Tint

There is no overload resolution. Names are not distinguished by argument count or type; the first declaration whose name matches wins.

Value form

float L = Luma(BaseColor);
vec3  C = Common::ApplyTint(BaseColor, Tint);
vec3  N = F_Normal(uv, Output="Normal");
KindRequirements
FunctionExactly one declared output, otherwise DreamShader Function '{Name}' has {Count} outputs and must be called with explicit out variables, for example {Name}(..., ResultA, ResultB). The argument count must equal the declared input count exactly.
GraphFunctionThe same two rules, plus an active Graph build context. Internally the call is rewritten as a statement call whose out target is a generated temporary named __ds_<function name>_value<N>.
ShaderFunction family, VirtualFunctionAny output count. Exactly one output is selected — implicitly when only one is declared, otherwise with Output= / OutputName= / OutputIndex=. Optional inputs may be omitted or passed as default.

A call result is an ordinary postfix expression, so it can be swizzled directly: SampleTexture2D(Albedo, uv).rgb.

Statement form

F_PulseTint(BaseColor, Tint, TintedColor, PulseAmount);

Multi-output ShaderFunction / VirtualFunction statement calls are available since 1.3.5.

Arguments are inputs first, then one out target per output, in declaration order. Every declared output must receive a target — there is no way to discard one.

KindArity rule
Function, GraphFunctionexact: inputs + outputs. DreamShader Function '{Name}' expects {Total} arguments ({Inputs} inputs, {Outputs} out targets) but got {Got}.
ShaderFunction family, VirtualFunctionat least as many arguments as outputs; the leading arguments − outputs are inputs and must not exceed the declared input count. Every input not covered must be declared opt, otherwise {Kind} '{Name}' is missing required input '{Input}'.

Out-target rules

RuleFailure
each out target is a plain variable name — not an expression, a member path or a swizzleDreamShader Function '{Name}' out argument {Index} must be a plain variable name. (1-based)
the name is non-empty after trimmingDreamShader Function '{Name}' has an empty out target name.
the names are distinct within one call, compared case-sensitivelyDreamShader Function '{Name}' cannot write multiple out results into '{Target}' in the same call.

An out target does not need to exist beforehand and does not need to be declared. Each target is bound in the current scope to the call's output with that output's declared shape, replacing any previous value of that name without a type check. Declare the variable first if a specific width matters.

Out targets are compared case-sensitively for uniqueness, but Graph variables are looked up case-insensitively. F(a, Result, result) therefore passes the uniqueness check and then binds two entries whose later reads resolve to whichever the case-insensitive scan finds first. Use one spelling.

Arguments

Positional and named

CalleePositionalNamedMixing
Function, value or statementrequiredrejected: DreamShader Function '{Name}' currently uses positional arguments only.
GraphFunction, value or statementrequiredrejected: DreamShader GraphFunction '{Name}' currently uses positional arguments only.
ShaderFunction family / VirtualFunction, value formallowedallowedforbidden{Kind} '{Name}' input arguments cannot mix positional and named forms.
ShaderFunction family / VirtualFunction, statement formrequiredrejected: {Kind} '{Name}' statement calls currently use positional arguments only.
input-bearing parameterrejected: Parameter '{Name}' must be called with named arguments wiring its input pins (e.g. {Name}(Coordinates=...) or {Name}(Input=...)).required
StaticSwitchParameterallowed — index 0 is the true branch, index 1 the false branchallowed — True/A, False/Ballowed

Argument names are normalised before comparison: trimmed, then lower-cased. Coordinates=, coordinates= and COORDINATES = are the same argument. Positional arguments are indexed among the unnamed arguments only, so a positional index skips over any named argument that precedes it.

The no-mixing rule is per call, not per argument: if any argument is named, no positional argument may appear. A named argument matching no declared input fails with {Kind} '{Name}' does not have an input named '{Argument}'.; too many positional arguments fail with {Kind} '{Name}' received {Got} positional input argument(s), but only {Declared} input(s) are declared.

Every input argument is coerced to the input's declared type, with the usual silent narrowing — see Conversions.

default

since 1.2.3 default is a bare identifier, matched case-insensitively, that explicitly requests an optional input's declared default instead of supplying a value.

SituationBehaviour
default for an input declared optthe input pin is left unconnected; the function's own default applies
default for a required input{Kind} '{Name}' input '{Input}' is not optional and cannot use default.
default in a Function / GraphFunction callnot recognised — evaluated as an ordinary identifier and fails with Unknown Graph identifier 'default'.

Omitting a trailing optional input entirely has the same effect as passing default.

float3 tinted = F_Tint(BaseColor, default, Output="OutColor");

Output selection

Output, OutputName and OutputIndex are reserved named arguments on the value form of ShaderFunction, ShaderLayer, ShaderLayerBlend and VirtualFunction calls. They are removed from the input argument list before inputs are bound.

ArgumentAcceptsMeaning
Outputa literalselect the output whose declared name matches, case-insensitively
OutputNamea literalexact synonym of Output
OutputIndexan integer literalselect by 0-based index into the declared Outputs list
float value = F_MultiOutput(Input=Mask, Output="Height");
float other = F_MultiOutput(Input=Mask, OutputIndex=1);
RuleFailure
Output/OutputName and OutputIndex are mutually exclusive{Kind} '{Name}' cannot use OutputName/Output together with OutputIndex.
neither given while more than one output is declared{Kind} '{Name}' exposes multiple outputs. Specify Output="Name" or OutputIndex=N.
OutputIndex must be an integer literal within range{Kind} '{Name}' OutputIndex is out of range.
Output / OutputName must be a literal, not an expression{Kind} '{Name}' OutputName must be a literal value.
the name must match a declared output{Kind} '{Name}' does not expose an output named '{Output}'.
the selected output must exist on the loaded asset — matched by name first, then by ordinal{Kind} '{Name}' output '{Output}' does not exist on MaterialFunction asset '{Asset}'.

These three names are not available on Function or GraphFunction calls, which reject named arguments outright — a multi-output Function must use the statement form with explicit out targets. They are also unavailable in the statement form of every kind, for the same reason.

UE.Expression(…) accepts the same three selectors with the same rules; see UE.Expression.

BreakOutFloatNComponents

A VirtualFunction call whose declared name is BreakOutFloat2Components, BreakOutFloat3Components or BreakOutFloat4Components (case-insensitive) is inlined as a swizzle instead of generating a MaterialFunctionCall node, provided it has a usable first input argument and an output selector:

Output nameChannel
x, r0
y, g1
z, b2
w, a3

OutputIndex= selects the same channel by index. If any precondition is unmet — a default first argument, both selectors given, no selector at all — the call falls through to the ordinary MaterialFunctionCall path.

Calling a parameter

Input-pin wiring

since 1.4.1 A declared parameter whose node owns input pins may be "called" to wire those pins. The call materialises the parameter node exactly as a bare reference does and connects each named argument to the input pin of the same name. The node is cached under the parameter's name, so a later bare reference shares the configured node.

The complete list of parameter node types that accept this form:

ChannelMaskParameterStaticComponentMaskParameterTextureSampleParameter2D
TextureSampleParameter2DArrayTextureSampleParameterCubeTextureSampleParameterCubeArray
TextureSampleParameterVolumeTextureSampleParameterSubUVRuntimeVirtualTextureSampleParameter
SparseVolumeTextureSampleParameter

Pin names use the same normalisation as argument names. Every argument must be named, and every value must be numeric.

Only the TextureSampleParameter* family — and the other node types above — own input pins. A compact Texture2D property is a texture object parameter and has none, so it cannot be called this way.

Asset slots are not call arguments either. The texture, curve or font a sampler parameter points at is set with [TextureSlot=Path(…)]-style declaration metadata. Passing one as a call argument fails with Parameter '{Name}' ({NodeType}) has no input pin named '{Argument}'. Asset slots (Texture/Curve/Font/...) are set via [{Argument}=Path(...)] metadata, not call arguments. See Property Types and Metadata and Groups.

Properties = {
    TextureSampleParameter2D Albedo = Path(Game, "Textures/T_Albedo");
}

Graph = {
    vec2 UV  = UE.TexCoord(Index = 0);
    vec4 Tex = Albedo(Coordinates = UV);
}

StaticSwitchParameter

since 1.2.3 A StaticSwitchParameter property does not resolve as a bare identifier — it needs its two branches, so it is readable only through the call form.

BranchArgument names, in lookup order
trueTrue=, then A=, then positional index 0
falseFalse=, then B=, then positional index 1

Both branches must be present, must not be texture objects or Substrate values, must agree on the MaterialAttributes flag, and must have the same component count.

Properties = {
    StaticSwitchParameter UseDetail = true [
        Group="Switches";
        SortPriority=30;
    ];
}

Graph = {
    vec3 Albedo = UseDetail(True = DetailColor, False = BaseColor);
}

Because the switch is resolved at shader-permutation time rather than per pixel, this is the cheap alternative to an if when the choice is a material setting rather than a computed value.

Recursion and nesting

SituationBehaviour
GraphFunction calling itself, directly or indirectlydetected at build time: GraphFunction cycle detected: {Path}. with the active call stack joined by ->; names compared case-insensitively
Function marked SelfContained / Inline calling itselfSelfContained Function cycle detected: {Path}. HLSL Custom nodes cannot compile recursive DreamShader functions.
nested calls in one expressionlegal — arguments are ordinary expressions, so F(G(x), 2.0) works for any callable kind
a call used as an Outputs binding expressionlegal — bindings are full expressions

Which calls are reused

Call kindDeduplicated
ShaderFunction / ShaderLayer / ShaderLayerBlend / VirtualFunctionyes, keyed on the argument list plus the asset path, with a second key per selected output — so the node is shared across calls that differ only in which output they read
generic reflected UE.* (UE.Expression, and any UE.<Name> that is not a registered sugar builtin) and every Substrate.* callyes
math builtinsyes
registered UE.* sugar builtins and UE.CollectionParamno — dispatched before any key is computed, so every call site gets a fresh node
Function and GraphFunctionno — the generated Custom node is built fresh per call site
UE.Expression whose resolved class derives from UMaterialExpressionCustomno — explicitly exempt
StaticSwitchParameter callsno

Because positional arguments are keyed by index, F_Tint(a, b) and F_Tint(b, a) are different keys; because argument names are normalised, F_Tint(Color = a) and F_Tint(color = a) are the same. If any argument produces no key token, the whole call is not cached and the node is always created.

Diagnostics

MessageCauseFix
Unknown Graph function '{Name}'.The call name matched nothing on any surface — often a misspelled constructor such as vec1 or mat3.Details
Graph calls must target a named function.The callee is not an identifier or a :: / . qualified name.
Graph call '{Name}' is ambiguous because multiple definitions use that name: {Kinds}.Two or more declaration kinds declare the same name.Rename one of them, or qualify the Function with its Namespace.
Graph expression statements currently support only Function calls with explicit out arguments.A statement that is not a call at all.Details
Graph expression statement '{Name}' is unsupported. Only DreamShader Function, GraphFunction, ShaderFunction, ShaderLayer, ShaderLayerBlend, or VirtualFunction calls may use statement syntax.A statement call to a builtin, a constructor or a parameter.Assign the result instead: v = UE.Time();
DreamShader Function '{Name}' has {Count} outputs and must be called with explicit out variables, for example {Name}(..., ResultA, ResultB).Value form on a multi-output Function.Use the statement form with one out target per output.
DreamShader Function '{Name}' returns one value and expects {Expected} input argument(s) when used as a value expression, but got {Got}.Wrong argument count in the value form.
DreamShader Function '{Name}' currently uses positional arguments only.A named argument on a Function or GraphFunction call.
DreamShader Function '{Name}' out argument {Index} must be a plain variable name.An out target that is not a bare identifier.
DreamShader Function '{Name}' cannot write multiple out results into '{Target}' in the same call.The same out target used twice.
DreamShader Function '{Name}' input '{Input}': {Error}An input argument failed to evaluate or to coerce to the declared type.Details
GraphFunction cycle detected: {Path}.Direct or indirect GraphFunction recursion.
DreamShader GraphFunction '{Name}' contains an unterminated UE.* call.A UE. call in the body has no closing ).
{Kind} '{Name}' input arguments cannot mix positional and named forms.Both argument forms present in one value call.
{Kind} '{Name}' is missing required input '{Input}'.A non-opt input received no argument.Pass a value, or declare the input opt.
{Kind} '{Name}' exposes multiple outputs. Specify Output="Name" or OutputIndex=N.Value form on a multi-output material function with no selector.
{Kind} '{Name}' does not expose an output named '{Output}'.No declared output has that name.
{Kind} '{Name}' output '{Output}' does not exist on MaterialFunction asset '{Asset}'.The declaration and the loaded asset disagree about the outputs.Regenerate the asset, or fix the declaration.
{Kind} '{Name}' could not load MaterialFunction asset '{Asset}'.The generated or referenced asset is missing.Details
VirtualFunction '{Name}' asset reference is invalid: {Error}Options.Asset did not resolve to a UMaterialFunction.Details
Parameter '{Name}' must be called with named arguments wiring its input pins (e.g. {Name}(Coordinates=...) or {Name}(Input=...)).A positional argument in a parameter pin call.
Parameter '{Name}' ({NodeType}) has no input pin named '{Argument}'. Asset slots (Texture/Curve/Font/...) are set via [{Argument}=Path(...)] metadata, not call arguments.The argument name matches no input pin on the node.Details
StaticSwitchParameter '{Name}' requires True=... and False=... inputs.One or both branches missing.
StaticSwitchParameter '{Name}' branches must have the same component count, got {Left} and {Right}.The two branches have different widths.

The complete cross-stage list is in the diagnostics index.

Example

import "Helpers.dsh";

Shader(Name="Docs/M_Calls")
{
    Properties {
        vec3                     Tint    = vec3(1.0, 0.4, 0.1);
        TextureSampleParameter2D Albedo  = Path(Game, "Textures/T_Albedo");
        StaticSwitchParameter    UseTint = true;
    }

    Settings {
        Domain       = "Surface";
        ShadingModel = "Unlit";
    }

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

    Graph {
        vec2 UV  = UE.TexCoord(Index = 0);

        // Parameter pin call: wires the sampler's Coordinates pin.
        vec4 Tex = Albedo(Coordinates = UV);

        // Value form, single-output Function declared in Helpers.dsh.
        float L  = Luma(Tex.rgb);

        // Value form, namespaced Function.
        vec3 Lit = Common::ApplyTint(Tex.rgb, Tint);

        // Statement form: two out targets, inputs first. The targets need no declaration.
        PulseTint(Lit, Tint, Pulsed, Amount);

        // StaticSwitchParameter call selects between the two.
        Color = UseTint(True = Pulsed, False = vec3(L, L, L));
    }
}

Helpers.dsh:

Function float Luma(in vec3 color) { return dot(color, float3(0.299, 0.587, 0.114)); }

Function PulseTint(in vec3 color, in vec3 tint, out vec3 result, out float amount) {
    amount = 0.5 + 0.5 * sin(color.r * 6.28318);
    result = color * tint * amount;
}

Namespace(Name="Common")
{
    Function ApplyTint(in vec3 color, in vec3 tint, out vec3 result) {
        result = color * tint;
    }
}
TextureCoordinate                      -> UV
TextureSampleParameter2D  Albedo       -> Tex          (Coordinates pin wired to UV)
Custom  "Luma"                         -> L
Custom  "Common::ApplyTint"            -> Lit
Custom  "PulseTint"                    -> Pulsed (output 0), Amount (output 1)
StaticSwitchParameter  UseTint         -> Color

The two additional output pins on the PulseTint Custom node are named after the caller's out variables, not after the declared result names.

See also

On this page