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
| Construct | What its text is |
|---|---|
a .dss function with /// @custom | HLSL, 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 entry | HLSL 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 material | declarations, 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 spelling | Accepted | Notes |
|---|---|---|
float, float1, half, double | yes | 1 component |
int, uint, bool | yes | 1 component; all collapse to the same float widths in the graph |
float2..4, half2..4, double2..4, int2..4, uint2..4, bool2..4 | yes | 2 / 3 / 4 components |
Texture2D, TextureCube, Texture2DArray, Texture3D | yes | texture objects; valid as a function input, never as a result (DSH6254) |
VolumeTexture | yes | written as Texture3D in the generated HLSL |
SamplerState | yes | a sampler since 2.0.0 (1.x read it as a Texture2D) |
float2x2, float3x3, float4x4 | inside an HLSL body only | there are no matrix values in a graph; see Matrices below |
struct | in a .dss only | fields 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 DSH4361Matrix 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.
| Rule | HLSL | .dss function | 1.x Function |
|---|---|---|---|
| Parameter qualifiers | in, out, inout | in, out, inout | in and out only — inout is DSH6302 |
| Default arguments | none | yes — float Strength = 1.0; on an exported function it makes the input optional | none (opt and defaults belong to ShaderFunction Inputs) |
| Return type | anything, plus void | anything | optional; if present it means exactly one output and forbids any out parameter (DSH6304) |
| Multiple results | out params alongside a return | out params | out params with no return type |
| Overloading | supported | not supported — one name declares one thing (DSH4210) | the same |
| Named like a builtin | may shadow it | DSH6206 | DSH6206 |
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
| Where | Control flow available |
|---|---|
a /// @custom / Function / GraphFunction body | all 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 code | if / 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 block | if / 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
| Context | What is available |
|---|---|
a /// @custom / Function / GraphFunction body | the full HLSL intrinsic set, plus everything Unreal's Common.ush brings into scope |
graph code — .dss and a 1.x Graph | the 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.
| Written | Becomes |
|---|---|
vec2 / vec3 / vec4 | float2 / float3 / float4 |
ivec2..4, uvec2..4, bvec2..4 | int2..4, uint2..4, bool2..4 |
mat2 / mat3 / mat4 | float2x2 / float3x3 / float4x4 |
mix | lerp |
fract | frac |
mod | fmod |
A::B | A_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 guard | DREAMSHADER_BUILTINS_USH |
| Defines | 25 DS_* symbols — 22 macros and 3 functions |
| Emitted by the plugin | no |
#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).
| Symbol | Kind | Expansion | Node equivalent |
|---|---|---|---|
DS_TIME | macro | (View.GameTime) | UE.Time() |
DS_REAL_TIME | macro | (View.RealTime) | — |
DS_DELTA_TIME | macro | (View.DeltaTime) | — |
DS_PERIODIC_TIME(Period) | macro | (fmod(View.GameTime, (Period))) | UE.Time(Period=…) |
DS_TexCoord(Parameters, CoordinateIndex) | float2 function | Parameters.TexCoords[CoordinateIndex].xy, or float2(0, 0) when NUM_TEX_COORD_INTERPOLATORS is 0 | UE.TexCoord |
DS_VertexColor(Parameters) | float4 function | Parameters.VertexColor | UE.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 function | UV + float2(frac(Time * Speed.x), frac(Time * Speed.y)) | UE.Panner |
DS_SampleTexture2D(Tex, UV) | macro | Texture2DSample(Tex, Tex##Sampler, UV) | SampleTexture2D(tex, uv) in a Graph block |
DS_SampleTexture2DLod(Tex, UV, Lod) | macro | Texture2DSampleLevel(Tex, Tex##Sampler, UV, Lod) | — |
DS_SampleTexture2DBias(Tex, UV, Bias) | macro | Texture2DSampleBias(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_PerInstanceRandomandDS_PerInstanceFadeAmountare meaningful only for instanced or GPU-culled draws. On a plain mesh they read back safe constants (0and1respectively) 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.
| Category | Keywords | In DreamShaderLang |
|---|---|---|
| Types | void, bool, int, uint, half, float, double | all but void are type tokens; a .dss function may return void |
| Control flow | if, 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 |
| Jumps | return, break, continue, discard | .dss: all of them, but discard in a branch is DSH4362; a 1.x Graph: none |
| Parameters | in, out, inout | .dss: all three; 1.x: in and out only |
| Modifiers | const, static, uniform, groupshared | .dss: uniform is a material parameter, static const a constant; 1.x: const in Properties |
| Textures | Texture2D, TextureCube, SamplerState, SamplerComparisonState | the first three, as type tokens |
| Aggregates | struct | in a .dss only |
Where to next
GLSL Overview
GLSL aliases and how they are rewritten.
Keyword Cheatsheet
DreamShaderLang, HLSL and GLSL vocabularies compared.
DreamShaderLang 2.0
.dss: declarations, functions, @custom bodies.
Functions
1.x Function, GraphFunction and Namespace in full.
Math Builtins
Every math name graph code understands.
Types and Values
The closed token set and its validity matrix.
Custom Pass examples
Seven complete .dsp pipelines with the .dss materials they use — highlight outline, UI frosted glass, X-ray, wind field, scanner pulse, old CRT, tagged objects. Needs UE 5.8.
GLSL Overview
GLSL for DreamShaderLang authors — which GLSL spellings are first-class tokens in a 1.x source, which are rewritten inside Function bodies, which carry a warning, why a .dss takes HLSL only, and what has no equivalent at all.