Source Files
The three DreamShaderLang file kinds, what each may declare, and how imports assemble them into one translation unit.
DreamShaderLang lives in plain text files of three kinds. They share one parser and one grammar; the extension decides only two things — what the file is allowed to declare, and whether compiling it produces an asset.
| Extension | Name | Role |
|---|---|---|
.dsm | Dream Shader Material | Material entry point. Holds the translation unit's one Shader block. |
.dsf | Dream Shader Function since 1.3.5 | Asset entry point for material functions and layers. |
.dsh | Dream Shader Header | Shared declarations only. Generates nothing; consumed through import. |
Extensions are compared case-insensitively, so M_Water.DSM is a material file.
Synopsis
// <name>.dsm — Dream Shader Material
[ import "<specifier>" ; ]…
[ Shader( Name = "…" [, Root = "…"] ) { … } ] // at most one per translation unit
[ <any function, layer, virtual-function or namespace block> ]…// <name>.dsf — Dream Shader Function
[ import "<specifier>" ; ]…
[ { ShaderFunction | ShaderLayer | ShaderLayerBlend }( Name = "…" [, Root = "…"] ) { … } ]…
[ { VirtualFunction | Namespace | Function | GraphFunction } … ]…// <name>.dsh — Dream Shader Header
[ import "<specifier>" ; ]…
[ { VirtualFunction | Namespace | Function | GraphFunction } … ]…| Notation | Meaning | Example |
|---|---|---|
<x> | Placeholder — substitute a real value; the angle brackets are not typed. | Name = <string> |
[ x ] | Optional — the whole group may be left out. | [, Root = <string>] |
{ a | b } | Choice — take exactly one of the alternatives separated by |. | { Node( … ) | Comment( … ) } |
… | Repetition — the preceding item may appear any number of times. | <property-declaration> … |
What each kind may contain
| Top-level block | .dsm | .dsf | .dsh |
|---|---|---|---|
Shader | yes | no | no |
ShaderFunction | yes | yes | no |
ShaderLayer | yes | yes | no |
ShaderLayerBlend | yes | yes | no |
MaterialLayer (deprecated) | yes | yes | no |
MaterialLayerBlend (deprecated) | yes | yes | no |
VirtualFunction | yes | yes | yes |
Namespace | yes | yes | yes |
Function | yes | yes | yes |
GraphFunction | yes | yes | yes |
import | yes | yes | yes |
.dsm has no restriction at all — its column is simply what the grammar accepts. See
Top-level Blocks for what each one declares.
How the restriction is enforced
The rule is not part of the parse. After a file's import lines have been stripped and before
the declaration parser runs, the loader scans the remaining text for literal substrings, compared
case-insensitively.
| File kind | Rejected when the text contains |
|---|---|
.dsh | Shader( or ShaderFunction( or ShaderLayer( or ShaderLayerBlend( or MaterialLayer( or MaterialLayerBlend( |
.dsf | Shader( |
.dsm | — |
One needle is enough for .dsf because ShaderFunction( does not contain the substring Shader(;
.dsh needs all six for the same reason, and because MaterialLayerBlend( does not contain
MaterialLayer(.
Because this is a substring scan, the forbidden text is rejected wherever it appears — inside a
line comment, a block comment, or a string literal. A .dsh containing the comment
// see Shader(Name="…") is rejected.
The converse also holds. Shader ( with a space before the parenthesis contains no forbidden
substring and passes the scan, and the parser then accepts it, because it consumes the keyword and
the ( as separate tokens. A block name of your own that ends in the same characters — MyShader(
— trips the .dsf rule.
The scan applies to each file's own text. When A imports B, B is checked against B's extension
rule and A against A's. A .dsh that imports a .dsf full of ShaderFunction( blocks therefore
passes, and the imported blocks are compiled as part of the translation unit. The kind rules
constrain what you write in a file, not what its import closure ends up containing.
One translation unit
A translation unit is one source file plus everything it imports, transitively. import directives
are inlined into a single text before parsing, which has one consequence worth memorising:
At most one Shader block per translation unit, not per file. Importing two files that each
declare a Shader fails with Only one top-level Shader block is currently supported., even though
neither file breaks the rule on its own.
Everything else — Function name collisions, Namespace re-opening, VirtualFunction visibility —
is likewise closure-wide. See Imports and Namespaces.
What each kind generates
| Kind | Compiled as | Produces |
|---|---|---|
.dsm | material entry point | the UMaterial (or thin instance) of its Shader block, plus every function asset it declares |
.dsf | asset entry point | the function assets it declares |
.dsh | not an entry point | nothing — a header only reaches the compiler through import |
A .dsm with no Shader block is still compilable: it produces whatever function assets it does
declare.
Where files are found
Files are discovered by recursive scans of the source directory.
| Scan | Extensions | Excluded | Used for |
|---|---|---|---|
| All source files | .dsm, .dsf, .dsh | everything under the packages directory | dependency graph, workspace generation, editor file lists |
| Generatable files | .dsm, .dsf | .dsm files under the packages directory | batch compile / generate-all |
The two exclusions are not symmetric. The all-sources scan drops every file under the packages
directory; the generatable scan drops only package .dsm files. A .dsf shipped inside
DShader/Packages is therefore still picked up as a generatable file and will produce function
assets on a generate-all.
| Directory | Default | Project setting |
|---|---|---|
| Source | <Project>/DShader | Source Directory |
| Packages | <Source>/Packages | derived from Source Directory; not separately configurable |
| Generated shaders | <Project>/Intermediate/DreamShader/GeneratedShaders | Generated Shader Directory |
A configured relative path is resolved against the project directory, and all three directories are created at module startup. See Project Settings and Packages.
Example
<Project>/DShader/
├── Materials/
│ └── M_Water.dsm
├── Functions/
│ └── F_Tint.dsf
├── Shared/
│ └── Common.dsh
└── Packages/
└── @typedreammoon/
└── dream-noise/
└── Library/
└── Noise.dsh// DShader/Shared/Common.dsh — header: helpers only
Namespace(Name="Common")
{
Function ApplyTint(in vec3 color, in vec3 tint, out vec3 result) {
result = color * tint;
}
}// DShader/Functions/F_Tint.dsf — function file: may declare ShaderFunction
import "Shared/Common.dsh";
ShaderFunction(Name="Functions/F_Tint")
{
Inputs = {
vec3 InColor;
opt float Strength = 1.0;
}
Outputs = {
vec3 OutColor;
}
Graph = {
OutColor = InColor * Strength;
}
}// DShader/Materials/M_Water.dsm — material file: may declare Shader
import "Functions/F_Tint.dsf";
Shader(Name="Materials/M_Water")
{
Outputs = {
vec3 Color;
Base.EmissiveColor = Color;
}
Graph = {
Color = vec3(0.1, 0.3, 0.6);
}
}Compiling M_Water.dsm produces both assets:
/Game/Materials/M_Water UMaterial
/Game/Functions/F_Tint UMaterialFunctionNotes
- A
.dshis the natural home forVirtualFunctiondeclarations. The editor's Create Virtual Function action writes one underDShader/VirtualFunctions. - Nothing stops a
.dsfor.dshfrom being imported by any other kind. The extension decides only the default extension of an unsuffixed import specifier and the content rule applied to that file. - The content check lives in the editor source loader. The runtime parser entry point is extension-agnostic.
Diagnostics
| Message | Cause | Fix |
|---|---|---|
| DreamShader header '{Path}' may only declare Function/Namespace/GraphFunction/VirtualFunction blocks and imports. | A .dsh whose text contains one of the six forbidden substrings — comments and strings included. | Move the block into a .dsf or .dsm, or reword the comment. |
| DreamShader function file '{Path}' may only declare imports, Function/Namespace/GraphFunction/VirtualFunction blocks, and ShaderFunction/ShaderLayer/ShaderLayerBlend blocks. | A .dsf whose text contains Shader(. | Move the Shader block into a .dsm. |
| Only one top-level Shader block is currently supported. | A second Shader block anywhere in the import closure. | Split the materials into separate translation units. Details |
| A top-level Shader, Function, GraphFunction, Namespace, ShaderFunction, ShaderLayer, ShaderLayerBlend, or VirtualFunction block was not found. | The translation unit declares no top-level block. An empty Namespace does not satisfy this. | Declare at least one block. Details |
| DreamShader could not read '{Path}'. | The file is in the dependency graph but could not be loaded. | |
| DreamShader import '{Specifier}' referenced from '{Path}' could not be resolved. | No candidate path existed inside its containment root. | Details |
| DreamShader import cycle detected at '{Path}'. | A file imported itself, directly or transitively. | Details |
Where to next
Lexical Elements
Tokens, the case-sensitivity matrix, literals, statement splitting.
Top-level Blocks
The seven blocks a source file may declare, and what each generates.
Imports and Namespaces
How several files become one translation unit.
Packages
The DShader/Packages layout and the @scope/name convention.