DreamShaderLang
HLSL / GLSL Background

HLSL Overview

HLSL for DreamShaderLang authors — where HLSL actually runs, how types and signatures map, what you can write in a @custom or Function body, and the shipped DreamShaderBuiltins.ush header.

HLSL is the shading language Unreal's material Custom node speaks. DreamShaderLang is not an HLSL compiler, but HLSL is not decoration here either: the body of a /// @custom function in a .dss, and of a 1.x Function / GraphFunction, is HLSL, written almost verbatim into the generated Custom node and compiled by Unreal. The rest of a .dss looks like HLSL but is lowered to a material node graph.

This page is for readers arriving from HLSL: which habits carry over, which do not, and where the line is.

Where HLSL runs, and where it does not

ConstructWhat its text is
a .dss function with /// @customHLSL, captured verbatim; each call becomes one UMaterialExpressionCustom node whose code is the body
1.x Function Name(…) { … }HLSL — the legacy front end reads it as a /// @custom function
1.x GraphFunction Name(…) { … }HLSL, except every UE.* call inside is lifted out into a real material node and wired back in as an extra input pin
the other .dss functions and the entryHLSL syntax, not HLSL execution — bound and lowered to material nodes; with if, ?:, && and loops of provable length
1.x Graph = { … }not HLSL — a small statement language that builds UMaterialExpression nodes
Properties, Inputs, Outputs, Settings, uniform, #pragma materialdeclarations, not code

since 2.0.0 An HLSL body is written into the Custom node's own code; the other functions it calls are embedded as DreamShaderFn_* members of a generated wrapper struct. No per-source .ush include is generated any more. See Pipeline.

So: arithmetic that is awkward as a node graph goes in HLSL; engine inputs, parameters and bindings stay in graph code. The two meet at a function call.

// plain HLSL
float3 ApplyTint(float3 color, float3 tint)
{
    return color * tint;
}
// .dss — the same helper, as one Custom node
/// @custom
float3 ApplyTint(float3 color, float3 tint)
{
    return color * tint;
}
// 1.x — the same helper
Function ApplyTint(in vec3 color, in vec3 tint, out vec3 result) {
    result = color * tint;
}

In 1.x a declared return type is legal too and lowers to a single output, so Function float3 ApplyTint(in vec3 color, in vec3 tint) { return color * tint; } is the HLSL-style spelling of the same helper.

Types

DreamShaderLang type tokens are a closed set. The HLSL spellings it accepts:

HLSL spellingAcceptedNotes
float, float1, half, doubleyes1 component
int, uint, boolyes1 component; all collapse to the same float widths in the graph
float2..4, half2..4, double2..4, int2..4, uint2..4, bool2..4yes2 / 3 / 4 components
Texture2D, TextureCube, Texture2DArray, Texture3Dyestexture objects; valid as a function input, never as a result (DSH6254)
VolumeTextureyeswritten as Texture3D in the generated HLSL
SamplerStateyesa sampler since 2.0.0 (1.x read it as a Texture2D)
float2x2, float3x3, float4x4inside an HLSL body onlythere are no matrix values in a graph; see Matrices below
structin a .dss onlyfields may carry their own /// block and array dimensions; no methods, no nesting

A few tokens HLSL does not know: the .dss material (a material attribute set), and the 1.x MaterialAttributes, StaticBool and Substrate. As a function result, MaterialAttributes is written as FMaterialAttributes and StaticBool as bool since 2.0.0; as a Custom node input, MaterialAttributes is DSH6210 and Substrate is DSH6253 in any position — a Custom node has no pins of either kind. Write such pipelines as ordinary graph code, a ShaderFunction or a GraphFunction. The full validity matrix is on Types and Values.

Matrices

A graph has no matrix values. float2x2 / float3x3 / float4x4 — and mat2 / mat3 / mat4, which 1.x normalizes to them — are DSH3252 in Properties and DSH4361 anywhere else they would become a node or a pin. A 1.x Function signature does accept mat3 lexically, so the declaration parses and the call fails:

Function float3 Rotate(in mat3 basis, in vec3 v) { return mul(basis, v); }
// parses; a Graph call that passes a matrix to 'basis' is DSH4361

Matrix work belongs inside a /// @custom / Function body, as locals, or goes through the transform builtins — UE.TransformVector and UE.TransformPosition, see UE.* Nodes.

Signatures

HLSL function signatures and DreamShaderLang function signatures look alike, but the rules differ in places.

RuleHLSL.dss function1.x Function
Parameter qualifiersin, out, inoutin, out, inoutin and out only — inout is DSH6302
Default argumentsnoneyes — float Strength = 1.0; on an exported function it makes the input optionalnone (opt and defaults belong to ShaderFunction Inputs)
Return typeanything, plus voidanythingoptional; if present it means exactly one output and forbids any out parameter (DSH6304)
Multiple resultsout params alongside a returnout paramsout params with no return type
Overloadingsupportednot supported — one name declares one thing (DSH4210)the same
Named like a builtinmay shadow itDSH6206DSH6206

A 1.x function must produce something: at least one out parameter, or a return type. Neither is DSH6305.

Function float Luma(in vec3 color) {
    return dot(color, float3(0.299, 0.587, 0.114));      // one result, HLSL style
}

Function SplitChannels(in vec4 src, out vec3 rgb, out float alpha) {
    rgb   = src.rgb;                                      // two results, out style
    alpha = src.a;
}

since 2.0.0 return is kept as written: the body now is the body of the HLSL function that returns the node's first output, so return expr; at any brace depth returns what HLSL says it does (1.x rewrote only the depth-0 returns, and a nested one bypassed __return). 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 with no return at all (the warning DSH6257; the output is 0).

Textures and samplers

A texture-typed input is one pin carrying the texture object, and the engine declares SamplerState <ParamName>Sampler beside it. Sample with that name inside the body:

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;
}

since 2.0.0 tex.Sample(uv) works too — as do SampleLevel, SampleBias and SampleGrad with one argument fewer than HLSL's own form — and is rewritten to the engine's Texture2DSample(tex, texSampler, uv) family. An embedded helper's signature gains SamplerState <Name>Sampler after each texture parameter, and a call to it from another body passes the matching argument; such a call must pass every argument (DSH6262), with a plain name as the texture argument (DSH6263). SamplerState is not in the set that triggers this expansion.

Control flow

WhereControl flow available
a /// @custom / Function / GraphFunction bodyall of HLSL's — if, else, for, while, do, switch, break, continue, discard. The body is passed through, so what compiles in a Custom node compiles here
.dss graph codeif / else, ?:, && / ||; for / while / do are unrolled when the trip count is provable (64 at most by default), and are DSH4360 otherwise — a loop whose count is known only at run time goes in a /// @custom function. No switch; discard inside a branch is DSH4362
a 1.x Graph blockif / else only, with a parenthesised condition and braced branches

&&, ||, %, ?:, &, |, ^, << and v[i] in a 1.x Graph block are errors with a code (DSH2200–DSH2210) since 2.0.0, no longer silent truncations. In a Function body and in a .dss they are ordinary operators. See Unsupported Constructs.

Intrinsics

ContextWhat is available
a /// @custom / Function / GraphFunction bodythe full HLSL intrinsic set, plus everything Unreal's Common.ush brings into scope
graph code — .dss and a 1.x Graphthe math builtins: 44 HLSL names, matched by exact spelling; a 1.x source also takes four GLSL spellings (the warning DSH5277). Everything else through UE.* / UE.Expression(…)

since 2.0.0 distance, exp, exp2, log, log2, log10, tan, sign, round, trunc, ddx, ddy, fwidth, rcp, rsqrt and more are builtins now. Still absent: sinh / cosh / tanh (no material node — write them in a @custom body) and matrices.

The Sine / Cosine nodes that graph sin / cos build take cycles (Period = 1); only sin / cos in an HLSL body take radians. round rounds half to even, as HLSL does since 2.1.2.

Naming your own function after a math builtin (lerp, dot, pow, …) is DSH6206 at the declaration since 2.0.0. Through 1.9.x it compiled and generated, but was unreachable from a Graph block. Constructor names (float3, vec4, …) are reserved the same way.

What happens to your body text

Every 1.x Function and GraphFunction body goes through an identifier-level rewrite before it is stored; the scan is comment- and string-aware. A .dss /// @custom body is kept verbatim, without this pass.

WrittenBecomes
vec2 / vec3 / vec4float2 / float3 / float4
ivec2..4, uvec2..4, bvec2..4int2..4, uint2..4, bool2..4
mat2 / mat3 / mat4float2x2 / float3x3 / float4x4
mixlerp
fractfrac
modfmod
A::BA_B — exactly a namespaced function's own name, so Ns::Fn(…) inside a body reaches it since 2.0.0

The match is whole identifier, case-insensitive. A local, helper or struct member called Mix, Mod, Fract, Vec3 or Mat4 inside a 1.x body is renamed silently. There is no diagnostic; the failure shows up as an HLSL compile error or as silently different math. Rename it — MixColor, ModValue.

An #include at the top of a body (only whitespace and comments before it) is not part of the function: it is blanked out of the body (with spaces, so lines and columns still match) and put on the include list of every Custom node whose code holds the body, which the engine includes at file scope. An #include after a statement stays in the body. The full two-way alias table is on GLSL Overview.

DreamShaderBuiltins.ush

The plugin ships an HLSL header that defines DS_* macros and functions mirroring the HLSL the material translator emits for the corresponding UE.* nodes.

On disk<Plugin>/Shaders/DreamShaderBuiltins.ush (shipped in the release archive)
Virtual shader path/Plugin/DreamShader/DreamShaderBuiltins.ush
Include guardDREAMSHADER_BUILTINS_USH
Defines25 DS_* symbols — 22 macros and 3 functions
Emitted by the pluginno
#include "/Plugin/DreamShader/DreamShaderBuiltins.ush"

At module startup DreamShader registers a shader source directory mapping from the virtual directory /Plugin/DreamShader to the plugin's Shaders folder, unless that virtual directory is already mapped. The mapping is unconditional — it does not depend on any project setting, backend choice or engine version — so the path above always resolves for the engine's shader preprocessor. It is a separate mapping from /DreamShaderGenerated, where since 2.0.0 no compile writes any more.

Nothing in the plugin emits an include for this header, and nothing references any DS_* symbol. A Custom node made from a Function carries only the #include lines at the head of its body; neither the Graph nor the ThinCustom backend includes it. Every symbol has a node equivalent in the UE.* catalogue, so the header is only useful in hand-written HLSL. Its own comment says it is included by generated DSI_*.ush files — the arrangement of the retired Instance backend; see Backend.

Symbols

Parameters is the material parameter struct in scope at the include site; Tex is a texture parameter identifier, from which the sampler name is derived by token pasting (Tex##Sampler).

SymbolKindExpansionNode equivalent
DS_TIMEmacro(View.GameTime)UE.Time()
DS_REAL_TIMEmacro(View.RealTime)—
DS_DELTA_TIMEmacro(View.DeltaTime)—
DS_PERIODIC_TIME(Period)macro(fmod(View.GameTime, (Period)))UE.Time(Period=…)
DS_TexCoord(Parameters, CoordinateIndex)float2 functionParameters.TexCoords[CoordinateIndex].xy, or float2(0, 0) when NUM_TEX_COORD_INTERPOLATORS is 0UE.TexCoord
DS_VertexColor(Parameters)float4 functionParameters.VertexColorUE.VertexColor
DS_CameraVector(Parameters)macro((Parameters).CameraVector)UE.CameraVector
DS_ReflectionVector(Parameters)macro((Parameters).ReflectionVector)UE.ReflectionVector
DS_PixelNormalWS(Parameters)macro((Parameters).WorldNormal)UE.PixelNormalWS
DS_VertexNormalWS(Parameters)macro((Parameters).TangentToWorld[2])UE.VertexNormalWS
DS_TwoSidedSign(Parameters)macro((Parameters).TwoSidedSign)UE.TwoSidedSign
DS_ViewportUV(Parameters)macro(GetViewportUV(Parameters))UE.ViewportUV
DS_PixelDepth(Parameters)macro(GetPixelDepth(Parameters))UE.PixelDepth
DS_WorldPosition(Parameters)macro(WSDemote(GetWorldPosition(Parameters)))UE.WorldPosition
DS_TranslatedWorldPosition(Parameters)macro(GetTranslatedWorldPosition(Parameters))UE.TranslatedWorldPosition
DS_ObjectPosition(Parameters)macro(WSDemote(GetObjectWorldPosition(Parameters)))UE.ObjectPosition
DS_ObjectRadius(Parameters)macro(GetPrimitiveData(Parameters).ObjectRadius)UE.ObjectRadius
DS_ObjectBounds(Parameters)macro(float3(GetPrimitiveData(Parameters).ObjectBoundsX, …ObjectBoundsY, …ObjectBoundsZ))UE.ObjectBounds
DS_CameraPosition(Parameters)macro(WSDemote(GetWorldCameraOrigin(Parameters)))UE.CameraPosition
DS_PerInstanceRandom(Parameters)macro(GetPerInstanceRandom(Parameters))UE.PerInstanceRandom
DS_PerInstanceFadeAmount(Parameters)macro(GetPerInstanceFadeAmount(Parameters))UE.PerInstanceFadeAmount
DS_Panner(UV, Time, Speed)float2 functionUV + float2(frac(Time * Speed.x), frac(Time * Speed.y))UE.Panner
DS_SampleTexture2D(Tex, UV)macroTexture2DSample(Tex, Tex##Sampler, UV)SampleTexture2D(tex, uv) in a Graph block
DS_SampleTexture2DLod(Tex, UV, Lod)macroTexture2DSampleLevel(Tex, Tex##Sampler, UV, Lod)—
DS_SampleTexture2DBias(Tex, UV, Bias)macroTexture2DSampleBias(Tex, Tex##Sampler, UV, Bias)—

The WSDemote calls lower the engine's large-world-coordinate vectors to float3. The camera-relative position form is already float3 and keeps precision near the camera.

Using it safely

Put the #include first in the body. It is then put on the Custom node's include list and included at file scope, so the header's three function definitions (DS_TexCoord, DS_VertexColor, DS_Panner) compile normally. An #include written after a statement stays in the body and is pasted inside the generated function — there the 22 macros still work (#define is legal anywhere), but the three function definitions are not.

DS_TexCoord and DS_VertexColor depend on translator side effects that nothing arranges any more. DS_TexCoord reads Parameters.TexCoords[…], which exists only when at least one interpolator slot was allocated during material translation; with none allocated the function compiles and returns float2(0, 0). DS_VertexColor reads Parameters.VertexColor, which is fed only when the material was translated with vertex-colour usage. The retired Instance backend arranged both; Graph and ThinCustom do not. Use UE.TexCoord and UE.VertexColor, or a wired function input, instead.

  • The remaining reads are side-effect free. Every field and Get*(Parameters) helper they touch is populated unconditionally at pixel entry, so they compile in any opaque Surface pixel evaluation with no usage flag or interpolator request.
  • DS_PerInstanceRandom and DS_PerInstanceFadeAmount are meaningful only for instanced or GPU-culled draws. On a plain mesh they read back safe constants (0 and 1 respectively) with no compile error.
  • The include guard makes repeated inclusion in one translation unit harmless.
  • The 1.x GLSL-alias rewrite skips string literals, so the path in #include "…" is never rewritten.
Function vec2 Wobble(in vec2 uv, in float speed)
{
    #include "/Plugin/DreamShader/DreamShaderBuiltins.ush"
    float t = DS_TIME * speed;
    return uv + float2(sin(t), cos(t)) * 0.02;
}

The node-based equivalent needs no include and no HLSL helper at all:

GraphFunction vec2 Wobble(in vec2 uv, in float speed)
{
    float t = UE.Time() * speed;
    return uv + vec2(sin(t), cos(t)) * 0.02;
}

Common HLSL keywords

For reference — this is HLSL's own vocabulary, all valid inside a /// @custom or Function body. The In DreamShaderLang column marks what is understood outside a body.

CategoryKeywordsIn DreamShaderLang
Typesvoid, bool, int, uint, half, float, doubleall but void are type tokens; a .dss function may return void
Control flowif, else, for, while, do, switch, case, default.dss: all but switch / case; a 1.x Graph: if / else only; default is also a call argument in 1.x
Jumpsreturn, break, continue, discard.dss: all of them, but discard in a branch is DSH4362; a 1.x Graph: none
Parametersin, out, inout.dss: all three; 1.x: in and out only
Modifiersconst, static, uniform, groupshared.dss: uniform is a material parameter, static const a constant; 1.x: const in Properties
TexturesTexture2D, TextureCube, SamplerState, SamplerComparisonStatethe first three, as type tokens
Aggregatesstructin a .dss only

Where to next

On this page