DreamShaderLang
Generation

The Pipeline

How one DreamShaderLang source file becomes Unreal assets — every stage, what triggers a build, and where each stage can fail.

DreamShaderLang does not compile to bytecode or to an executable. It compiles to Unreal assets: a UMaterial or a UDreamShaderMaterialInstance, any number of material-function assets, and one generated HLSL include. This page follows one source file all the way through.

AspectValue
Inputone .dsm or .dsf file, plus the transitive closure of its import directives
Rejected input.dsh — a header never generates assets directly
ProducesUMaterial / UDreamShaderMaterialInstance, UMaterialFunction, UMaterialFunctionMaterialLayer, UMaterialFunctionMaterialLayerBlend, and one .ush helper include
Runs inthe editor process only — the generator uses editor-only material APIs

There is no runtime path. Nothing builds a material from DreamShaderLang in a packaged game; by then the assets already exist.

Stages

One compile of one file runs this sequence. Every step is a gate: the first failure aborts the compile, and its message is what comes back as the compile result.

#StageWhat it doesFails with
1Normalize pathfull path, NormalizeFilename, MakeStandardFilename
2File-kind gate.dsh never generatesDreamShader header '{File}' does not generate assets directly. Recompile dependent .dsm or .dsf files instead.
3Load prepared sourcerecursive import inlining into one textDreamShader import '{Import}' referenced from '{File}' could not be resolved. · DreamShader import cycle detected at '{File}'. · DreamShader could not read '{File}'.
4Content gateper-file substring scan, applied to each file's own text with its import lines blankedDreamShader header '{File}' may only declare Function/Namespace/GraphFunction/VirtualFunction blocks and imports. · DreamShader function file '{File}' may only declare imports, Function/Namespace/GraphFunction/VirtualFunction blocks, and ShaderFunction/ShaderLayer/ShaderLayerBlend blocks.
5Parsethe whole prepared text, as one parse unitany parse diagnostic
6HashCRC32 of the prepared text — the source hash
7.dsf gatea .dsf may not declare a top-level Shader{File}: .dsf files cannot define top-level Shader blocks.
8Write helper includeonly when the unit declares at least one FunctionFailed to write generated helper include '{Path}'. · DreamShader Function '{Name}' is declared more than once.
9Material-function assetsone asset per ShaderFunction / ShaderLayer / ShaderLayerBlend, in declaration order{Kind} '{Name}' must declare at least one output. · {Kind} '{Name}' must provide a Graph block. · asset-creation errors
10Material assetonly when the unit declares a top-level Shader{File}: Outputs block is required. · Unsupported Backend '{Value}'. Supported values: Graph, Instance, ThinCustom. · asset-creation errors
11Compose resultthe success text, plus a Warnings: block listing every parser warning when any were emitted

Throughout this section, {Placeholder} marks a value the compiler substitutes at runtime.

Two consequences are worth pulling out of that table:

  • The parse unit is the import closure, not the file. import directives are inlined before parsing, so "one Shader per file" is really "one Shader per closure" — and the source hash in step 6 covers every imported byte. See Imports and Namespaces.
  • Material functions are generated before the material. A Shader can therefore call a ShaderFunction declared beside it in the same file.

Import lines are replaced by blank lines, and each inlined file is bracketed by // Begin DreamShader source: <path> / // End DreamShader source: <path> markers, so reported line and column numbers still point into the file you actually edited.

Steps 9 and 10 share the same graph builder. Requesting only the material — what the editor's compile material entry point does — runs steps 1–7 and 10, skipping 9.

Inside step 10 — the material

#Sub-stageFails with
1Reject .dsh and .dsfDreamShader source '{File}' cannot generate a material asset directly.
2Require a top-level Shader{File}: This file does not define a top-level Shader block.
3Require a non-empty Outputs{File}: Outputs block is required.
4Validate Settings, then Outputssee Material Settings and Output Bindings
5Detect Base.FrontMaterial / Base.MaterialAttributes{File}: Base.FrontMaterial and Base.MaterialAttributes cannot be used by the same Shader.
6Write the helper include — both backends need itas step 8 above
7Resolve the backendUnsupported Backend '{Value}'. Supported values: Graph, Instance, ThinCustom.
8Create or reuse the target assetsee Asset Paths
9Source-hash short circuit(when it fires, the rest is skipped)
10Build the graph, apply settings, lay out, recompilesee Regeneration
11Persist, or clear the dirty flag in memory-only modeGenerated DreamShader asset '{Path}' could not be saved.

The backend is resolved at sub-stage 7, before any material object exists and before the rest of Settings is validated. An unrecognized Backend value therefore fails first, and no other settings diagnostic is reported for that file. See Backend.

Inside the graph build

Shared by both backends. Under ThinCustom it runs against the hidden base material, not the emitted instance.

  1. Clear the existing expressions, then reset every material property to its engine default.
  2. Apply Settings.
  3. Force Substrate shading when Base.FrontMaterial is bound.
  4. Reject duplicate Properties names — compared ignoring case.
  5. Seed one MakeMaterialAttributes node per uninitialized MaterialAttributes output declaration.
  6. Build the body — either the Graph block, or, when the unit has no Graph and no initialized output, one whole-surface Custom node.
  7. Connect each Outputs binding.
  8. Lay the graph out. Skipped in memory-only mode, and skipped for graphs of 1200 or more expressions unless a Layout section is present.
  9. Recompile the material.

Step 1 is why editing a generated asset by hand is futile — see Regeneration.

What triggers a build

TriggerForcedTargetResult
Auto-compile on save — file watcher plus debouncenomemoryhash-skip active; the common path
Generate all in-memory materials — editor startup, and every change to Default Compiler Backendyesmemoryevery project source recompiled
Material Content Browser Compile / thumbnail-refresh buttonsyesmemoryone source
Live preview rendereryesmemoryone source
Materialize, and creating a child instance of a memory-only materialyesdiskone source, persisted
Commandlet -run=DreamShadercaller's choice — -Forcediskpersisted
Cook, on the cook director process onlyyesdiskevery project source persisted

Auto-compile is governed by two project settings: Auto Compile On Save (default on) and Save Debounce Seconds (default 0.25, clamped to [0.05, 10.0]). Turning the first off makes the source-directory watcher ignore file changes entirely. See Project Settings.

The interactive editor never writes a per-material .uasset. The source file is the authoring surface; generated assets live in memory until a cook, the commandlet, or an explicit Materialize puts them on disk. This surprises almost everyone the first time — In-memory Materials explains it in full.

Outcomes that are not assets

A source file can compile successfully and still produce nothing you can place in the Content Browser.

Result messageOutcome
Generated DreamShader helper include '{Path}' from {File}.the unit declared only Function blocks — success
DreamShader file '{File}' contains VirtualFunction declarations only; no assets were generated.success
DreamShader file '{File}' contains GraphFunction declarations only; no assets were generated.success
DreamShader file '{File}' did not contain any material, ShaderFunction, ShaderLayer, or ShaderLayerBlend assets to generate.failure

The last row is the one to watch: a file that declares nothing generatable is an error, while a file that declares only helpers is fine.

Success messages

MessageEmitted for
Generated {Kind} {AssetPath} from {File}.each ShaderFunction / ShaderLayer / ShaderLayerBlend
Generated {AssetPath} from {File}.{Suffix}a Graph-backend material; {Suffix} is (virtual) in memory-only mode
Generated DreamShader thin-custom material {AssetPath} from {File}.a ThinCustom-backend material
Skipped {AssetPath} from {File}; source hash is unchanged.the source-hash short circuit — see Regeneration
Generated DreamShader helper include '{Path}' from {File}.a unit with Function blocks and no assets

Parser warnings never fail a compile. They are appended to whichever message above applies, under a Warnings: header.

Progress reporting

Generation reports through Unreal's slow-task system. Dialogs are delayed so a fast compile never flashes one, and are suppressed entirely under IsRunningCommandlet().

ScopeTitleFramesDialog delay
whole fileCompiling DreamShader source '{File}'...60.35 s
one materialGenerating DreamShader material from '{File}'...110.25 s
one material functionGenerating DreamShader function '{Name}'...100.25 s
ThinCustom emissionGenerating thin-custom material for '{Name}'...8inherited
graph build (nested)Building material graph for '{Name}'...11inherited
automatic layoutLaying out DreamShader material graph...one per nodeinherited

Worked example

// DShader/Materials/M_Emissive.dsm
import "Common.dsh";

ShaderFunction(Name="Functions/F_Tint")
{
    Inputs  { vec3 InColor; vec3 InTint; }
    Outputs { vec3 OutColor; }
    Graph   { OutColor = InColor * InTint; }
}

Shader(Name="Materials/M_Emissive")
{
    Properties { vec3 Tint = vec3(1.0, 0.4, 0.1); }
    Settings   { ShadingModel = "Unlit"; }
    Outputs    { vec3 Color; Base.EmissiveColor = Color; }
    Graph      { Color = F_Tint(vec3(1.0, 1.0, 1.0), Tint); }
}

One compile of that file produces:

Intermediate/DreamShader/GeneratedShaders/M_Emissive_9f2c41ab.ush   (only if Common.dsh declares Function blocks)
/Game/Functions/F_Tint                                             UMaterialFunction
/Game/Materials/M_Emissive                                         UDreamShaderMaterialInstance + hidden UMaterial base

and reports:

Generated ShaderFunction /Game/Functions/F_Tint from I:/.../M_Emissive.dsm.
Generated DreamShader thin-custom material /Game/Materials/M_Emissive from I:/.../M_Emissive.dsm.

The .ush include lives under the Generated Shader Directory project setting (default Intermediate/DreamShader/GeneratedShaders) and is mapped to the virtual shader path /DreamShaderGenerated/. Its file name embeds a hash of the source path, not of the source text, so editing a Function body rewrites the same file instead of accumulating variants.

Where next

On this page