Function
Function
Function
DreamShader » DreamShaderLang » Function
A top-level block that declares a reusable HLSL helper: the body is HLSL text, kept verbatim, and
every call site becomes one UMaterialExpressionCustom node whose code is that body.
| Declared in | .dsm, .dsf, .dsh — and inside a Namespace body |
| Kind | top-level block |
| Generates | no asset and no file. Each call site is one Custom node; its code holds the body and every other Function the body calls (since 2.0.0: no .ush include is written) |
| Multiplicity | any number per file; one name declares one thing |
The legacy front end reads a Function as a /// @custom function of the 2.0 language,
with the SelfContained modifier or without it; dsc migrate writes it that way.
Synopsis
Function [ ( <generate-attributes> ) ] [ { Inline | SelfContained } ] [ <return-type> ] <name> ( [ <parameter-list> ] )
{
<hlsl>
}<parameter-list> ::= <parameter> [ , <parameter> ] …
<parameter> ::= [ { in | out } ] <type-token> <parameter-name>
<generate-attributes> ::= <key> = "<value>" [ , <key> = "<value>" ] … // Generate, Library, DescriptionThe keyword Function is matched case-sensitively. function, FUNCTION and Functions are not
the keyword: the file reports DSH2240, or
DSH2248 when what follows reads as a 2.0 declaration. See
Lexical elements.
Everything else in the declaration — the Inline / SelfContained modifier, the in / out
qualifiers, and every type token — is matched case-insensitively.
There is no Settings and no section of any kind. The { … } after the parameter list is raw HLSL,
captured verbatim: only its braces are read, and a brace inside a string or a comment does not count.
The optional ( … ) header (after 2.1.2) asks a package to generate a material function from the
function — Function(Generate = "MF_BL_Checker", Library = "Blender|Texture") Checker(...). It means
something only in a package's header: see Packages ▸ Asset headers.
Generate = "" opts a function out of its Namespace's template. Another key is the warning
DSH6331 and is ignored; outside a package the header is
DSH7234 and generates nothing. 1.x had no header on this block, so
no text that read before reads differently.
Declaration order
The parser reads identifiers left to right and disambiguates by lookahead:
| Form | Read as |
|---|---|
Function Name(…) | name |
Function Type Name(…) | return type, then name |
Function SelfContained Name(…) | modifier, then name |
Function SelfContained Type Name(…) | modifier, return type, then name |
Function Type SelfContained Name(…) | return type Type, name SelfContained, then a stray Name where ( belongs — DSH6319 |
The rule is mechanical: after one identifier, a ( makes it the name; another identifier makes it the
return type and the next identifier the name. The modifier is therefore fixed in first position.
Modifiers
| Modifier | Accepted on | Effect |
|---|---|---|
SelfContained | Function only | none: every Function node embeds the functions its body calls, which is what the modifier asked for in 1.x — see below |
Inline | Function only | the old spelling of SelfContained: the same, plus a warning (DSH6306) |
Both spellings are case-insensitive. On a GraphFunction either one is
DSH6307 (since 2.0.0; 1.x read the word as a return type).
[!WARNING] A function may not be named
InlineorSelfContained.Function SelfContained(in float a, out float b) { … }takes the modifier branch, then finds(where the name should be:DSH6300.
Parameters
A parameter is the words up to the next , or ). A word is an identifier or a keyword, so any
whitespace — tabs and line breaks included — only separates them. Two or three words are accepted:
| Words | Form | Qualifier |
|---|---|---|
| 2 | <type> <name> | defaults to in |
| 3 | <qualifier> <type> <name> | as written |
fewer than 2, more than 3, or anything that is not a word ([2], = 1.0) | — | DSH6301 |
| Rule | Behaviour |
|---|---|
| Qualifiers | Only in and out, compared in lower case, so In, OUT, iN all work. |
inout | Does not exist in a 1.x function: DSH6302. |
| Empty parameters | A lone , is skipped, so a trailing comma before ) is tolerated. |
| Order | Parameters keep their declaration order. in parameters become the node's input pins; out parameters its outputs. The two may be interleaved. |
| Reserved name | A parameter named __return (ignoring case) is DSH6303. |
| Duplicate name | DSH4214. |
| Not available | opt, default values, and [ … ] metadata blocks are not part of this grammar (DSH6301). They belong to the Inputs / Outputs sections of ShaderFunction and VirtualFunction. |
At least one output
A Function must produce something: either at least one out parameter or a return type.
Neither is DSH6305.
Return type
| Form | Legal | Result |
|---|---|---|
Function Name(in T a, out U r) | yes | one output, r |
Function Name(in T a, out U r1, out V r2) | yes | r1 is the node's first output, r2 an additional output |
Function U Name(in T a) { return expr; } | yes | the returned value is the node's output |
Function U Name(in T a, out V r) | no | DSH6304 |
Function Name(in T a) | no | DSH6305 |
Function U Name(…) { return; } | no | a top-level bare return; under a return type: DSH6308 |
Function Name(…, out U r) { … return; … } | no | a return; at any depth in a function without a return type: DSH6256 (since 2.0.0) |
So: a return type implies exactly one output, and forbids every explicit out. Multiple outputs
require out parameters and no return type.
return lowering
(since 2.0.0) return is no longer rewritten. 1.x replaced every top-level return with an
assignment to a synthetic __return result; 2.0 keeps the body's return statements as written,
because the body now is the body of the HLSL function that returns the node's first output — the
Custom node's own, or the helper's when another body embeds it. A return expr; therefore returns the
value at any brace depth; the nested case that bypassed __return in 1.x behaves like the top-level
one.
What is still checked: a top-level bare return; under a return type (DSH6308), a return;
anywhere in a function without one (DSH6256), and a return type whose body contains no return at
all, which is a warning that the output will be 0
(DSH6257). A body that is one bare expression is given its
return instead. The name __return stays reserved (DSH6303).
Type tokens
The 15 GLSL aliases (vec2…vec4, ivec*, uvec*, bvec*, mat2…mat4) are normalised to their
HLSL spellings at parse time, on the return type and every parameter type, and a scalar or vector
spelled in another case (Float3) is read as its lower-case spelling. A token that names no type is
DSH4201 at the declaration (since 2.0.0; 1.x only failed when
the function was called). The full alias list is in Type tokens.
| Token(s) | Valid as in | Valid as out / return |
|---|---|---|
float, half, int, uint, bool, their vectors …2–…4, and the GLSL aliases | yes | yes — a node output is Float1…Float4 |
MaterialAttributes | no — DSH6210, and DSH6252 at the node | yes |
StaticBool, StaticBoolParameter | yes — read as bool | yes — read as bool |
Texture2D, TextureCube, Texture2DArray, Texture3D, VolumeTexture | yes | no — DSH6254 |
SamplerState | a sampler (since 2.0.0; 1.x read it as a Texture2D) | no — DSH6254 |
Substrate | no — DSH6253 | no — DSH6253 |
matrices (float3x3, mat3, …) | not across a node pin: DSH4361 at a graph call | same |
The generated HLSL spells every type the 2.0 way: vec3 is float3, MaterialAttributes is
FMaterialAttributes, and VolumeTexture is Texture3D.
[!NOTE]
Substrateis never usable on aFunctionor aGraphFunction: a Custom node has no Substrate pins in any engine version. Build the Substrate part in aShaderFunctionor in theShader'sGraph. See Substrate builtins.
Texture parameters and samplers
Five tokens are texture parameters: Texture2D, TextureCube, Texture2DArray, Texture3D,
VolumeTexture (case-insensitive).
| Where | What happens |
|---|---|
| On the node | a texture in parameter is one pin carrying the texture object; the engine declares SamplerState <Name>Sampler beside it. Use that name in the body. |
| In an embedded helper | its HLSL signature gains SamplerState <Name>Sampler right after each texture parameter, and every call to it from another body passes <argument>Sampler after the texture argument. |
| That call | has to pass every argument (DSH6262), and its texture argument has to be a plain name (DSH6263). |
A parameter called <Texture>Sampler | collides with the paired name: DSH6255. |
SamplerState is not a texture parameter and receives no companion.
(since 2.0.0) <Texture>.Sample(UV) on a texture parameter — and SampleLevel, SampleBias,
SampleGrad with one argument fewer than HLSL's own form — is rewritten to the engine's
Texture2DSample(<Texture>, <Texture>Sampler, UV) family. The native HLSL spellings stay as written.
Body normalisation
Every Function and GraphFunction body — and nothing else in the language — is passed through an
identifier-level rewrite before it is stored. The scan skips comments and "…" strings. Two things
happen:
- Alias substitution. Whole identifiers are matched case-insensitively against 18 entries: the
15 GLSL type aliases plus
mix→lerp,fract→frac,mod→fmod. - Qualified-name flattening. A token of the form
A::B(repeatable, no spaces around::) is replaced by the identifier-sanitized spelling of the whole thing —Common::Remap01becomesCommon_Remap01.
[!WARNING] Because the alias match is case-insensitive and applies to whole identifiers anywhere in the body, a helper of your own called
Mix,Mod,Fract,Vec3,Mat4— or a variable by one of those names — is silently renamed in the emitted HLSL. There is no diagnostic: theDSH5277warning for a GLSL spelling applies toGraphtext, not to a body. Rename the identifier, or spell it so it does not collide (MixColor,ModValue).
(since 2.0.0) The flattened Common_Remap01 is exactly the name a namespaced function has, so a
Ns::Fn(…) call inside a body now reaches that function. See
Namespace.
Includes
#include directives written at the top of the body — only whitespace and comments may come
before them — are not part of the function. They are blanked out of the body (overwritten with spaces,
so every later line and column still matches the source) and hoisted onto the include list
(IncludeFilePaths) of every Custom node whose code holds the body: the function's own nodes, and the
node of any other Function that embeds it, the embedded functions' includes first.
(since 2.0.0: there is no generated include to hoist into.)
Function float3 SampleWind(in float3 WorldPos)
{
#include "/Plugin/DreamWind/Shared/DreamWindShared.h" // hoisted
FDreamWindSample S = DW_SampleAnalytic(DW_UnpackParams(P0, P1, P2), WorldPos);
return S.Velocity;
}Both #include "…" and #include <…> are accepted; the path is stored as written, delimiters
removed, and must be a virtual shader path the engine can resolve (/Engine/…, /Plugin/<Name>/…).
The engine puts a node's includes at file scope, so a header that defines functions works. Duplicates
on one node collapse to one entry. An empty path is
DSH6258; an unterminated one is left in the body for the
shader compiler. Comments before the directives stay in the body.
The scan stops at the first token that is not a leading #include: a directive that follows a
statement is not hoisted — it lands inside the generated function, which only works for headers
made of macros. See DreamShaderBuiltins.ush.
Generated HLSL
(since 2.0.0) A call site's Custom node carries all of its HLSL in its own Code. The shape:
struct generated_wrapper_<Name>_<CRC32 %08X> // only when the body calls other Functions
{
<Type0> DreamShaderFn_<Callee>(<params>) // one member per embedded Function,
{ // a callee before its caller
<Out1> = (<Type1>)0; // every `out` but the first
<Type0> <Out0> = (<Type0>)0; // a function without a return type
// Begin DreamShader source: <file>
// DreamShader custom: <Callee> line <N>
<callee body, verbatim>
// End DreamShader source: <file>
return <Out0>;
}
};
generated_wrapper_<Name>_<CRC32 %08X> __ds_wrapper_<CRC32 %08X>;
<Type0> <Out0> = (<Type0>)0; // a function without a return type
// Begin DreamShader source: <file>
// DreamShader custom: <Name> line <N>
<body, verbatim; calls to embedded functions go through __ds_wrapper_<CRC32 %08X>>
// End DreamShader source: <file>
return <Out0>; // only when the body has no top-level return| Element | Rule |
|---|---|
| Symbol name | DreamShaderFn_ + the function's name with every non-[A-Za-z0-9_] character replaced by _ and runs of underscores collapsed. A namespaced Common::ApplyTint is the function Common_ApplyTint, so DreamShaderFn_Common_ApplyTint. |
| Why the prefix | An unprefixed Luminance(float3) would shadow the engine intrinsic from /Engine/Private/Common.ush when a sibling member calls it. |
| A helper's return value | its return type; for a function without one, its first out parameter, which is why one body may call a multi-output Function as a value. |
| Helper parameters | declaration order; a SamplerState <Name>Sampler after each texture parameter; every out but the first as out <Type> <Name>, zero-initialised. |
| The node's own body | not wrapped in a function of DreamShader's: it is the Custom node's code. Its first output is the returned value, or a local for the first out; every other out is an additional output, which the engine declares and zero-initialises. |
| Markers | the Begin / custom / End comment lines around each body let a shader-compile error be mapped back to its source line; no rewrite adds or removes a line inside a body. |
| Return | return 0.0; (or the first out) is appended only when the body has no top-level return; a body that is one bare expression is wrapped in return …;. |
The node's title (its Description) is the function's name — Ns::Fn for a member of a
Namespace. The Generated HLSL page describes the
.ush include 1.x wrote instead.
Inline / SelfContained mode
(since 2.0.0) Every call site's node holds the function's own body and every other Function that
body calls, transitively, as members of the generated_wrapper_* struct — HLSL has no nested
functions, and a Custom node's code is a function body. 1.x did this only for a SelfContained
function and otherwise referenced its shared include; 2.0 has no shared include, so every function
gets what SelfContained asked for, and the modifier is read and changes nothing. (2.0.0 – 2.1.0
read it as /// @custom selfcontained, which embeds nothing, and left each call it made for the
shader compiler.)
| Aspect | Every Function, with Inline / SelfContained or without |
|---|---|
| Where the body lives | in the code of every node that calls it, and as a wrapper member in every node whose body calls it |
Other Functions its body calls | embedded, a callee before its caller |
IncludeFilePaths | the leading includes of the body and of every embedded function |
| A call from the node's body to an embedded function | __ds_wrapper_<CRC>.DreamShaderFn_<Name>(…) |
| Sibling calls inside the wrapper | plain DreamShaderFn_*, with no this. qualifier |
| Recursion | DSH6260 |
Both CRC32 values hash the calling function's name. A .dss that wants a body taken exactly as
written, with nothing embedded, writes /// @custom selfcontained; a 1.x Function has no
spelling for that. A call from a body to something that is not a
Function — a ShaderFunction or a VirtualFunction — is
DSH6261; to a GraphFunction with lifted
UE.* calls, DSH6327. A call that matches a function only in
case is a warning (DSH6259) and is left for the shader compiler.
Calling a Function
Both call forms live in a Graph block and are specified in
Calling functions. In summary:
| Form | Rule |
|---|---|
x = Fn(a, b); — value call (since 1.3.1) | passes the inputs; its value is the function's first output — the return value, or the first out (legacy rule L3b). (since 2.0.0) also on a function with several outputs, whose other outputs are left unconnected; Fn(a, b).Out or Fn(a, b, Output = "Out") selects another. |
Fn(a, b, OutX, OutY); — statement call | the inputs, then one receiver per output, in declaration order (legacy rule L5). A receiver must be a variable (DSH4239) of the output's type (DSH4218); one nobody declared is declared there (DSH5283). |
Too many arguments are DSH4224; an input left out is
DSH4217. (since 2.0.0) Named arguments name a parameter
(DSH4216 for a name it does not have), and a call that uses
them is matched by name and declaration order rather than as inputs-then-receivers. The mangled
spelling DreamShaderFn_Luma(c) no longer names the function (since 2.0.0):
DSH4208.
Notes
- One name declares one thing. Two functions of one name — in a file, or in a file and a header it
imports — are
DSH4210; so areA::BandA_B, which are the same name. A function named like a builtin (lerp,saturate, …) isDSH6206. - Names are case-sensitive (since 2.0.0).
Lumaandlumaare two functions. AGraphcall that matches a function only in case still resolves when the match is unique, withDSH5275. - Headers are parsed on their own. An imported
.dshis not pasted into the file; its functions are declared into it. See import. - A
.dsmor.dsfcontaining onlyFunctionblocks is accepted and produces nothing. - On UE 5.3 the generated Custom nodes display their code in the material graph; from UE 5.4 onward
ShowCodeis set tofalseon every generated Custom node. - The legacy section-style body
Function Name { Inputs = { … } Code = { … } }is not a supported form. A{where the parameter list's(should be isDSH6300.
Diagnostics
Every diagnostic carries the line and column of the construct it is about. The tables say when each code is raised; the code's page has the message.
Parse time
| Code | Raised when |
|---|---|
DSH6300 | no name after Function, the modifier or the return type — including Function SelfContained( and the section-style Function Name { |
DSH6306 | Inline is used (warning) |
DSH6319 | no ( after the name, or no { after the parameter list |
DSH6301 | a parameter is not [in|out] Type Name |
DSH6302 | a qualifier other than in / out, inout included |
DSH6303 | a parameter is named __return |
DSH6304 | a return type and an out parameter |
DSH6305 | neither a return type nor an out parameter |
DSH6308 | a top-level bare return; under a return type |
DSH2150 | the parameter list or the body is never closed |
Binding
| Code | Raised when |
|---|---|
DSH4201 | a type token names no type |
DSH4210 | the name is declared twice |
DSH4214 | two parameters share a name |
DSH6206 | the name is a builtin's |
DSH6210 | a MaterialAttributes input |
Custom-node code
| Code | Raised when |
|---|---|
DSH6252 | a MaterialAttributes input on the node's own function |
DSH6253 | Substrate in the signature |
DSH6254 | a texture or sampler returned, or handed back through an out |
DSH6255 | two parameters end up with one HLSL name — usually <Texture>Sampler |
DSH6256 | a return; in a function without a return type |
DSH6257 | a return type, and no return in the body (warning) |
DSH6258 | a leading #include with an empty path |
DSH6259 | a call in the body matches a function only in case (warning) |
DSH6260 | functions whose bodies call each other in a cycle |
DSH6261 | the body calls a function that is not a Function |
DSH6262 | a call to a texture-taking function passes the wrong number of arguments |
DSH6263 | a texture argument of such a call is not a plain name |
DSH6265 | a call to any other function passes another number of arguments than its HLSL takes |
DSH6266 | the body reads the value of a call to a function whose HLSL returns nothing |
DSH6327 | the body calls a GraphFunction with lifted UE.* calls |
Call time
| Code | Raised when |
|---|---|
DSH4208 | the called name declares nothing |
DSH5275 | the called name matches only in case (warning) |
DSH4216 | a named argument names no parameter |
DSH4217 | an input is not passed |
DSH4224 | too many arguments |
DSH4239 | a receiver is not a variable |
DSH4218 | a receiver's type does not match its output |
DSH5283 | an undeclared receiver is declared (info) |
DSH4361 | a matrix crosses a node pin |
DSH8214 | the emitter could not create the Custom node |
{Name} in the messages is the function's name; for a member of a Namespace see
that page.
The complete cross-stage list lives in the diagnostics index.
Example
// DShader/Lib/Color.dsh
Function float Luma(in vec3 color)
{
return dot(color, float3(0.299, 0.587, 0.114));
}
Function SelfContained Remap01(in float value, out float result)
{
result = saturate(value * 0.5 + 0.5);
}
Function SampleTinted(in Texture2D tex, in vec2 uv, in vec3 tint, out vec3 rgb, out float alpha)
{
float4 texel = Texture2DSample(tex, texSampler, uv);
rgb = texel.rgb * tint;
alpha = texel.a;
}Used from a Shader:
import "Lib/Color.dsh"
Shader(Name="Materials/M_Tinted")
{
Properties = {
Texture2D BaseTex = Path(Game, "Textures/T_Base");
vec3 Tint = vec3(1.0, 0.6, 0.2);
}
Outputs = {
vec3 Color;
float Alpha;
Base.EmissiveColor = Color;
Base.Opacity = Alpha;
}
Graph = {
vec2 UV = UE.TexCoord(Index = 0);
vec3 Rgb;
float A;
SampleTinted(BaseTex, UV, Tint, Rgb, A);
float Key;
Remap01(Luma(Rgb), Key);
Color = Rgb * Key;
Alpha = A;
}
}Each call is a Custom node. The Luma node's code — note the body's own return:
// Begin DreamShader source: DShader/Lib/Color.dsh
// DreamShader custom: Luma line 4
return dot(color, float3(0.299, 0.587, 0.114));
// End DreamShader source: DShader/Lib/Color.dshThe SampleTinted node: pins tex, uv, tint; output rgb (Float3) and the additional output
alpha (Float1). Its code declares and returns the first out, and reads the sampler the engine
declares beside tex:
float3 rgb = (float3)0;
// Begin DreamShader source: DShader/Lib/Color.dsh
// DreamShader custom: SampleTinted line 14
float4 texel = Texture2DSample(tex, texSampler, uv);
rgb = texel.rgb * tint;
alpha = texel.a;
// End DreamShader source: DShader/Lib/Color.dsh
return rgb;See also
- GraphFunction — the variant whose
UE.*calls become real material nodes - Namespace —
Ns::Fnqualification and the flattenedNs_Fnname - Calling functions — value vs statement calls, argument rules
- The 2.0 language —
/// @custom, which aFunctionis read as - Generated HLSL — the
.ushinclude 1.x wrote forFunctionblocks - Type tokens — the complete token catalogue and the GLSL alias tables
- Inputs / Outputs / Results — the section parameter grammar, with
optand metadata - ShaderFunction — the block that generates a real
UMaterialFunctionasset - Source files — which of
.dsm/.dsh/.dsfmay hold aFunction - Lexical elements — comments, identifiers, and the case-sensitivity matrix
- HLSL library — helpers available inside a
Functionbody - Substrate builtins — why
Substrateis aGraphFunction/ShaderFunctionconcern - Diagnostics index — every code, by stage