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 |
| Kind | language |
| Generates | UMaterial, UMaterialFunction, UMaterialFunctionMaterialLayer, UMaterialFunctionMaterialLayerBlend |
| Version | the 1.x language, last released in 1.9.1; built by the 2.0 compiler (since 2.0.0) |
| Engines | Unreal 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, anddsc migraterewrites 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 grammar | Graph statement grammar | |
|---|---|---|
| Implemented by | the legacy front end in module DreamShaderLang | the same front end, over the 2.0 statement and expression parser |
| Runs | when a source is compiled, after the preprocessor | in the same parse, after every other section of the block |
| Input | the tokens of the whole file | the tokens between Graph = { and its } |
| Produces | 2.0 declarations: #pragma material, uniforms, the entry function or function asset, #pragma layout | the statements of that function's body |
| Numeric literals | number tokens; each section reads a token's text the way its consumer asks — see Numeric literals | number tokens |
| Comments | // and /* */, anywhere between tokens | the same |
| Statement separator | ; outside brackets | ; — see Statements |
| Punctuation accepted | { } ( ) [ ] ; = , . : and :: where a section's grammar names them | every operator of the 2.0 grammar is a token; what a 1.x Graph did not have is refused by name, DSH2200–DSH2222 |
| Error positions | the line and column of the construct, in every section | the 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 -> assetThe 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 elements | Comments, identifiers, case sensitivity, literals, statement splitting |
| Keyword index | Every reserved word, alias and identifier rewrite |
import | Search roots, packages, cycles |
| Preprocessor | The eight #if / #define directives and the define table (since 1.9.0) |
| Types | Type-token catalogue and per-context validity |
Top-level blocks
Shader | Generates a UMaterial |
ShaderFunction | Generates a UMaterialFunction |
ShaderLayer / ShaderLayerBlend | Generates native material layer functions (since 1.3.0) |
VirtualFunction | Declares an existing UMaterialFunction (since 1.2.0) |
Function | Reusable HLSL helper |
GraphFunction | HLSL helper whose UE.* calls become node inputs (since 1.3.1) |
Namespace | Groups helpers under Ns::Name |
Sections
Properties | Parameter, const and UE.* declarations, Group("…") scopes |
Inputs / Outputs / Results | Typed parameters of material functions |
| Output bindings | Base.<Property> = <value>; and Expression( … ).Pin[<i>] |
Settings | Material and material-function settings |
Options | VirtualFunction asset binding |
Layout | Node / Comment placement and #Region |
Graph language
The statement and expression language inside Graph = { … }.
| Graph overview | What a Graph block is and how it is evaluated |
| Statements | Every statement form |
| Declarations | Variables, initialisers, scope |
| Expressions and operators | Precedence, associativity, compound assignment |
| Literals | Numeric forms, suffixes, true / false |
| Constructors | float3( … ), vec4( … ), splatting |
| Swizzles | Channel sets, reorder, repeat |
| Conversions | Coercion and component-count rules |
if / else | Conditions and branch semantics |
MaterialAttributes | Member writes and reads (since 1.2.5) |
| Calls | Calling functions and parameter pins |
| Name resolution | Lookup order and shadowing |
| Node reuse | Common-subexpression deduplication |
| Unsupported constructs | Loops, return, ternary, %, comparisons, indexing |
Notes
- The same token can be read two ways.
1.0fis one float token everywhere. APropertiesdefault reads its text — a sign, digits, one., an exponent, an optionalf— sofloat Strength = 1.0f;andfloat Strength = 1e3;are fine, andfloat Strength = 0x1F;isDSH3254; aGraphexpression 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 aFunctionbody (that is HLSL,#includeincluded), and#Region/#EndRegionin any case. Anything else isDSH1035, mis-cased directives such as#IFamong 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
.dsmor.dsfmust contain at least oneShader,ShaderFunction,ShaderLayer,ShaderLayerBlend,Function,GraphFunction,NamespaceorVirtualFunctionblock, otherwise it isDSH2254. (since 2.0.0) ANamespacecounts even when it is empty, and a.dshmay 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
- Source files — which block kinds each of
.dsm/.dsf/.dshmay contain - Lexical elements — tokens, case rules, literals, splitting
- Keyword index — every reserved word with the page that documents it
import— how a header's declarations reach the file that imports it- Preprocessor — cutting the declaration layer with
#if, and the define table - Graph — the statement grammar
- DreamShaderLang 2.0 — the compiler pipeline, and the
.dsslanguage dsc migrate— 1.x sources to.dss- Diagnostics index — every code, by pipeline stage
- Generation — what the tree is turned into
- Getting started — a first
.dsmend to end