DreamShaderLang
Plugin referenceGeneration

Generated HLSL

Generated HLSL

Generated HLSL

DreamShader » Generation » Generated HLSL

The HLSL a compile writes for the Function blocks of a source. (since 2.0.0) It is written into the Custom node each call makes; no .ush include is generated.

Written whena Graph calls a Function — one Custom node per call
Written tothe Custom node's Code; its IncludeFilePaths carry only the #include lines hoisted out of the bodies
Containsthe called function's body, verbatim, and every Function that body calls, embedded in a wrapper struct
Through 1.xone /DreamShaderGenerated/<basename>_<hash>.ush per source, holding every Function, which each node included

A 1.x Function is read by the legacy front end as a /// @custom function, the 2.0 form of a Custom node with a verbatim body — see /// @custom bodies. A GraphFunction is one too, with its UE.* calls lifted out into graph nodes wired to extra inputs.

Synopsis

<node code>     := [ <wrapper> ] <body block>
<wrapper>       := struct <wrapper type> { <helper definition> … }; <wrapper type> <wrapper var>;
<wrapper type>  := generated_wrapper_ <SanitizeIdentifier(function)> _ <CRC32(function) as %08X>
<wrapper var>   := __ds_wrapper_ <CRC32(function) as %08X>
<helper symbol> := DreamShaderFn_ <SanitizeIdentifier(helper name)>
<body block>    := [ <first out> declared ] <begin marker> <custom marker> <body> <end marker> [ return … ]

function is the name of the Function the node is built for. The wrapper is written only when its body calls another Function.

Location

(since 2.0.0) There is no file to locate: the code lives in the node, inside the asset, and is derived from the source the build key hashes, so an unchanged source skips it like everything else.

The virtual directory /DreamShaderGenerated is still mapped to the Generated Shader Directory project setting (category Paths, default Intermediate/DreamShader/GeneratedShaders) when the module starts, and Tools ▸ DreamShader ▸ Clean Generated Shaders still deletes every *.ush under it — recursively, one file at a time, never the directory itself — and queues a full forced recompile. No compile writes there any more; a file found there was written by a 1.x build. A material saved by a 1.x build may still have Custom nodes that include one; rebuilding the material replaces them with nodes that carry their own code.

Node code layout

struct generated_wrapper_<Name>_<HASH>             // only when the body calls other Functions
{
	<helper definition>

	<helper definition>

};
generated_wrapper_<Name>_<HASH> __ds_wrapper_<HASH>;

<T> <first out> = (<T>)0;                           // only when the first result is an `out` parameter
// Begin DreamShader source: <project-relative path of the file that declares the function>
// DreamShader custom: <Name> line <N>
<body, verbatim>
// End DreamShader source: <same path>
return <first out>;                                 // only when the body needs one, see below
ElementRule
bodythe text between the braces, after the 1.x body normalisation. It is never re-indented, and no rewrite adds or removes a line, so a shader-compile error maps back to its source line; <N> is the line of the body's opening brace
pathproject-relative, so the code — and with it the shader key — is the same on every machine. A function declared in an imported .dsh names the header
first resultthe declared return type, or the first out parameter of a function that has none (legacy rule L9): that one is the node's primary output and the other out parameters are additional outputs
final returnadded when the body has no top-level return of its own or the function returns nothing: return <first out>;, else return 0.0;. A return type whose body never returns is DSH6257
inputseach in parameter is a node input; a texture input gets the engine's <name>Sampler beside it

Function name mangling

Every embedded helper is prefixed. Without it, a Function Luminance(float3) would redefine the engine's own Luminance from /Engine/Private/Common.ush and fail shader compilation with redefinition of 'Luminance'.

DreamShaderFn_ + SanitizeIdentifier(<Function name>)

SanitizeIdentifier applies these rules, in order:

#Rule
1every character outside [A-Za-z0-9_] becomes _
2an empty result becomes the literal DreamShaderSymbol
3an all-underscore result becomes the literal DreamShaderSymbol
4a first character that is not [A-Za-z_] gets a _ prepended
5runs of consecutive underscores collapse to one
DSL nameFunction nameEmitted symbol
LumaLumaDreamShaderFn_Luma
Common::ApplyTintCommon_ApplyTintDreamShaderFn_Common_ApplyTint
Common::Remap01Common_Remap01DreamShaderFn_Common_Remap01

A Function inside Namespace(Name="Common") is the function Common_ApplyTint, so a top-level Function Common_ApplyTint is the same name declared twice: DSH4210.

Per-function definition shape

An embedded helper, as a member of the wrapper:

	<RetType> DreamShaderFn_<Name>(<parameters>)
	{
		<out parameter> = (<T>)0;                   // every `out` parameter but the first result
		<T> <first out> = (<T>)0;                   // when the first result is an `out` parameter
// Begin DreamShader source: <path>
// DreamShader custom: <Name> line <N>
<body, verbatim>
// End DreamShader source: <path>
		return <first out>;                         // when the first result is an `out` parameter
	}
ElementRule
RetTypethe declared return type, or the type of the first out parameter when there is none
parametersevery parameter in declaration order but that first out; an out one as out <type> <name> (since 2.0.0; 1.x put the inputs first)
sampler pairingimmediately after each texture-typed parameter, an extra SamplerState <name>Sampler
other out parameterszero-initialized first: an HLSL out arrives uninitialized
bodyas in the node's own code: verbatim, between the markers

Texture-typed parameters, for sampler pairing, are these five tokens, compared case-insensitively:

Texture2DTextureCubeTexture2DArrayTexture3DVolumeTexture

SamplerState is not one of them; a parameter declared SamplerState gets no companion argument. A texture parameter T beside a parameter named TSampler is DSH6255.

A parameter is written with the 2.0 spelling of its declared type:

Declared tokenEmitted HLSL type
vec2…vec4, ivec*, uvec*, bvec*, mat2…mat4float2…float4, int*, uint*, bool*, float2x2…float4x4
a scalar, vector or matrix type in another case (Float3)lower case
VolumeTextureTexture3D
everything elseas declared

Includes on the node

The #include "…" lines at the start of a body are blanked in place — overwritten with spaces, so every position after them still matches the source — and added to the node's IncludeFilePaths instead: the embedded helpers' first, in the order they are embedded, then the function's own, each path once. An #include with an empty path is DSH6258. See Function ▸ Includes.

Self-contained functions

A node embeds the transitive closure of the Functions its body calls: each callee once, a dependency before its dependents. Recursion has no HLSL form; a cycle among them is DSH6260.

ElementForm
struct typegenerated_wrapper_<sanitized name>_<hash>
instance variable__ds_wrapper_<hash>
namethe Function the node is built for
hashCRC32 of the raw, unsanitized name, uppercase hex
call sites in the node's own code__ds_wrapper_<hash>.DreamShaderFn_<Name>(…)
call sites between embedded membersplain DreamShaderFn_<Name>(…), no qualifier
member separationone tab of indentation, one blank line between members

A call site is rewritten by name: the argument list stays as written, except that a <texture>Sampler argument is spliced in after each texture argument. The call has to pass exactly the arguments the callee's HLSL takes — HLSL has no default arguments, and a function no overloads here (DSH6262 when it carries a texture, DSH6265 otherwise) — and each texture argument has to be a plain name (DSH6263). A helper returns its first result, so a body calls it as a value — float r = Remap01(x); — and a call that passes that result as an argument as well, Remap01(x, r);, is reshaped as 1.x reshaped it: r = Remap01(x);. (2.0.0 – 2.1.2 left it as written, one argument too many for the shader compiler.) The value of a function that returns nothing — a /// @custom one declared void, not a 1.x one — cannot be read (DSH6266).

A namespace-qualified call inside a body works (since 2.0.0): the body normalisation flattens Common::Remap01( to Common_Remap01(, which is the namespaced function's own name, so it is found and embedded like any other call. Through 1.x it called an undefined Common_Remap01 and the shader failed to compile.

SelfContained (and its old spelling Inline, DSH6306) asked 1.x for the closure to be embedded; every node embeds it now without being asked, so the modifier changes nothing and the function is a plain /// @custom. (2.0.0 – 2.1.0 read it as /// @custom selfcontained, which embeds nothing: a Function it called was left for the shader compiler, DSH6264.)

Diagnostics

CodeRaised when
DSH4210two Functions with the same name in the file and the headers it imports — including a namespaced one and a top-level one that flatten to the same name
DSH6260the Functions a node embeds call each other in a cycle
DSH6259(warning) a body calls a name that differs from a Function only in case; the call is left as written
DSH6261a body calls a function that is not a Function or GraphFunction, such as a ShaderFunction
DSH6327a body calls a GraphFunction whose UE.* calls were lifted into inputs of its own node
DSH6262, DSH6263a call that passes a texture cannot be matched up with the callee's parameters
DSH6265a call passes another number of arguments than the callee's HLSL takes
DSH6266a body reads the value of a call to a function whose HLSL returns nothing
DSH6258an #include line with an empty path

A call in a body to a name that is no function of the file is left for the shader compiler.

Example

// DShader/M_Ramp.dsm
Namespace(Name="Common")
{
    Function float3 ApplyTint(in float3 color, in float3 tint) { return color * tint; }
}

Function SelfContained Remap01(in float value, out float result) {
    result = saturate(value * 0.5 + 0.5);
}

Shader(Name="Materials/M_Ramp")
{
    Properties { ScalarParameter Input = 0.25; vec3 Tint = vec3(1.0, 0.4, 0.1); }
    Settings   { Domain = "UI"; ShadingModel = "Unlit"; }
    Outputs    { vec3 Color; Base.EmissiveColor = Color; }
    Graph {
        float R;
        Remap01(Input, R);
        Color = Common::ApplyTint(vec3(R, R, R), Tint);
    }
}

The Remap01 call becomes a Custom node with input value and primary output result:

float result = (float)0;
// Begin DreamShader source: DShader/M_Ramp.dsm
// DreamShader custom: Remap01 line 7

    result = saturate(value * 0.5 + 0.5);
// End DreamShader source: DShader/M_Ramp.dsm
return result;

The Common::ApplyTint call becomes a Custom node with inputs color and tint; its body returns:

// Begin DreamShader source: DShader/M_Ramp.dsm
// DreamShader custom: Common_ApplyTint line 4
 return color * tint; 
// End DreamShader source: DShader/M_Ramp.dsm

Neither body calls another Function, so neither node has a wrapper and neither includes anything. A function that does call one carries it (hash values are illustrative):

Function float Luma(in float3 c) { return dot(c, float3(0.299, 0.587, 0.114)); }
Function float3 Desaturate(in float3 c, in float amount) { float l = Luma(c); return lerp(c, float3(l, l, l), amount); }
struct generated_wrapper_Desaturate_1A2B3C4D
{
	float DreamShaderFn_Luma(float3 c)
	{
// Begin DreamShader source: DShader/M_Gray.dsm
// DreamShader custom: Luma line 1
 return dot(c, float3(0.299, 0.587, 0.114)); 
// End DreamShader source: DShader/M_Gray.dsm
	}

};
generated_wrapper_Desaturate_1A2B3C4D __ds_wrapper_1A2B3C4D;

// Begin DreamShader source: DShader/M_Gray.dsm
// DreamShader custom: Desaturate line 2
 float l = __ds_wrapper_1A2B3C4D.DreamShaderFn_Luma(c); return lerp(c, float3(l, l, l), amount); 
// End DreamShader source: DShader/M_Gray.dsm

See also

On this page