DreamShaderLang
Plugin reference

DreamShaderLang

DreamShaderLang

DreamShaderLang

DreamShader » DreamShaderLang

The language read from .dsm, .dsf and .dsh source files: an outer declaration grammar that describes assets, and a statement grammar, embedded in Graph blocks, that describes the node graph inside them.

Declared in.dsm, .dsf, .dsh
Kindlanguage
GeneratesUMaterial, UMaterialFunction, UMaterialFunctionMaterialLayer, UMaterialFunctionMaterialLayerBlend
Versionthe 1.x language, last released in 1.9.1; built by the 2.0 compiler (since 2.0.0)
EnginesUnreal Engine 5.3 – 5.8; the accepted grammar is identical on every supported engine version

[!NOTE] New sources are written in DreamShaderLang 2.0 (.dss). A 1.x source keeps building through the same compiler, and dsc migrate rewrites one as .dss, proving the rewrite builds the same graph.

Synopsis

A source file is one file plus the headers it imports.

[ import "<specifier>" ; ]…

<top-level-block>…
<top-level-block> := { Shader | ShaderFunction | ShaderLayer | ShaderLayerBlend
                     | MaterialLayer | MaterialLayerBlend | VirtualFunction | Namespace }
                     ( <attribute> = <value> [, …] ) { <section>… }
                   | { Function [ SelfContained | Inline ] | GraphFunction }
                     [<return-type>] <name> ( [<parameter>, …] ) { <HLSL> }

<section>          := <section-name> [=] { <statement>… } [;]

Top-level blocks may appear in any order and, apart from Shader, any number of times. There is no separator between them.

Two grammars

(since 2.0.0) One lexer turns the whole file into tokens, and one legacy front end reads both grammars from them into the same tree the 2.0 parser builds — so a 1.x file then goes through the same binder, IR and emitter as a .dss. Through 1.9.x the two grammars were two separate machines, the declaration parser in the runtime module and the Graph expression parser in the generator, each with its own character scanner.

Declaration grammarGraph statement grammar
Implemented bythe legacy front end in module DreamShaderLangthe same front end, over the 2.0 statement and expression parser
Runswhen a source is compiled, after the preprocessorin the same parse, after every other section of the block
Inputthe tokens of the whole filethe tokens between Graph = { and its }
Produces2.0 declarations: #pragma material, uniforms, the entry function or function asset, #pragma layoutthe statements of that function's body
Numeric literalsnumber tokens; each section reads a token's text the way its consumer asks — see Numeric literalsnumber tokens
Comments// and /* */, anywhere between tokensthe same
Statement separator; outside brackets; — see Statements
Punctuation accepted{ } ( ) [ ] ; = , . : and :: where a section's grammar names themevery operator of the 2.0 grammar is a token; what a 1.x Graph did not have is refused by name, DSH2200–DSH2222
Error positionsthe line and column of the construct, in every sectionthe line and column of the construct

The #if family belongs to neither grammar: it is handled line by line before the file is lexed, so none of its keywords is one the parser knows. See Preprocessor. (since 2.0.0) import is a top-level declaration of the declaration grammar; through 1.9.x it too was a line-oriented directive, handled before parsing.

<file>.dsm
  │
  ├─ preprocessor           evaluate `#if` / `#ifdef` / `#elif` / `#else` / `#endif` against the
  │                         define table; cut untaken branches, preserving the line count;
  │                         `Function` bodies pass through untouched
  │
  ├─ legacy front end       tokens -> blocks, sections, `Graph` statements -> the 2.0 tree;
  │                         what 1.x read silently and built wrong is an error (DSH2200–DSH2222)
  │
  ├─ binder                 names, types, calls; an `import`ed `.dsh` is preprocessed and parsed on
  │                         its own and its declarations are declared into this file; the legacy
  │                         rules apply to 1.x text and say so (DSH5275–DSH5292)
  │
  ├─ IR                     lowering, passes, validation
  │
  └─ emitter                material nodes  ->  asset

The front end and the binder report all of their errors rather than the first, and a stage with errors stops the pipeline before the next one; the preprocessor stops at its first error. The preprocessor runs first, per file, which is what lets an #if decide whether an import is taken at all — and why a #define is local to the file that writes it.

Declaration language

Source files.dsm / .dsf / .dsh, what each may contain, discovery
Lexical elementsComments, identifiers, case sensitivity, literals, statement splitting
Keyword indexEvery reserved word, alias and identifier rewrite
importSearch roots, packages, cycles
PreprocessorThe eight #if / #define directives and the define table (since 1.9.0)
TypesType-token catalogue and per-context validity

Top-level blocks

ShaderGenerates a UMaterial
ShaderFunctionGenerates a UMaterialFunction
ShaderLayer / ShaderLayerBlendGenerates native material layer functions (since 1.3.0)
VirtualFunctionDeclares an existing UMaterialFunction (since 1.2.0)
FunctionReusable HLSL helper
GraphFunctionHLSL helper whose UE.* calls become node inputs (since 1.3.1)
NamespaceGroups helpers under Ns::Name

Sections

PropertiesParameter, const and UE.* declarations, Group("…") scopes
Inputs / Outputs / ResultsTyped parameters of material functions
Output bindingsBase.<Property> = <value>; and Expression( … ).Pin[<i>]
SettingsMaterial and material-function settings
OptionsVirtualFunction asset binding
LayoutNode / Comment placement and #Region

Graph language

The statement and expression language inside Graph = { … }.

Graph overviewWhat a Graph block is and how it is evaluated
StatementsEvery statement form
DeclarationsVariables, initialisers, scope
Expressions and operatorsPrecedence, associativity, compound assignment
LiteralsNumeric forms, suffixes, true / false
Constructorsfloat3( … ), vec4( … ), splatting
SwizzlesChannel sets, reorder, repeat
ConversionsCoercion and component-count rules
if / elseConditions and branch semantics
MaterialAttributesMember writes and reads (since 1.2.5)
CallsCalling functions and parameter pins
Name resolutionLookup order and shadowing
Node reuseCommon-subexpression deduplication
Unsupported constructsLoops, return, ternary, %, comparisons, indexing

Notes

  • The same token can be read two ways. 1.0f is one float token everywhere. A Properties default reads its text — a sign, digits, one ., an exponent, an optional f — so float Strength = 1.0f; and float Strength = 1e3; are fine, and float Strength = 0x1F; is DSH3254; a Graph expression reads the token itself. See Numeric literals.
  • The preprocessor is not part of either grammar. Eight lowercase directives — #if, #ifdef, #ifndef, #elif, #else, #endif, #define, #undef — are evaluated over the raw text before parsing and never reach either grammar (since 1.9.0). Outside those eight, a # line is passed through only where it has a claimant: anywhere inside a Function body (that is HLSL, #include included), and #Region / #EndRegion in any case. Anything else is DSH1035, mis-cased directives such as #IF among them. See Preprocessor and Layout.
  • Top-level block keywords and the lower-case reserved words are the case-sensitive tokens. Section names, type tokens, attribute keys, settings keys and every other keyword-like token are matched case-insensitively. See Case sensitivity.
  • The parser is engine-version independent. No lexical or grammatical construct is gated on the Unreal Engine version. Version gates exist only downstream of parsing — Substrate types and builtins (UE 5.4+), and a small number of node and setting gates.
  • A .dsm or .dsf must contain at least one Shader, ShaderFunction, ShaderLayer, ShaderLayerBlend, Function, GraphFunction, Namespace or VirtualFunction block, otherwise it is DSH2254. (since 2.0.0) A Namespace counts even when it is empty, and a .dsh may hold nothing but imports.

Example

One .dsm exercising both grammars: the declaration grammar owns everything outside Graph = { }, the statement grammar everything inside it.

Shader(Name="Materials/M_Comments")
{
    Settings = {
        Domain       = "UI";        // trailing line comment
        ShadingModel = "Unlit";
    }

    Outputs = {
        vec3 Color;
        Base.EmissiveColor = Color;
    }

    Graph = {
        Color = vec3(1.0, 1.0, 1.0); // inline comment inside the graph body
    }
}

See also

On this page