DreamShaderLang
Language

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.

ExtensionNameRole
.dsmDream Shader MaterialMaterial entry point. Holds the translation unit's one Shader block.
.dsfDream Shader Function since 1.3.5Asset entry point for material functions and layers.
.dshDream Shader HeaderShared 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 } … ]…
NotationMeaningExample
<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
Shaderyesnono
ShaderFunctionyesyesno
ShaderLayeryesyesno
ShaderLayerBlendyesyesno
MaterialLayer (deprecated)yesyesno
MaterialLayerBlend (deprecated)yesyesno
VirtualFunctionyesyesyes
Namespaceyesyesyes
Functionyesyesyes
GraphFunctionyesyesyes
importyesyesyes

.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 kindRejected when the text contains
.dshShader( or ShaderFunction( or ShaderLayer( or ShaderLayerBlend( or MaterialLayer( or MaterialLayerBlend(
.dsfShader(
.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

KindCompiled asProduces
.dsmmaterial entry pointthe UMaterial (or thin instance) of its Shader block, plus every function asset it declares
.dsfasset entry pointthe function assets it declares
.dshnot an entry pointnothing — 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.

ScanExtensionsExcludedUsed for
All source files.dsm, .dsf, .dsheverything under the packages directorydependency graph, workspace generation, editor file lists
Generatable files.dsm, .dsf.dsm files under the packages directorybatch 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.

DirectoryDefaultProject setting
Source<Project>/DShaderSource Directory
Packages<Source>/Packagesderived from Source Directory; not separately configurable
Generated shaders<Project>/Intermediate/DreamShader/GeneratedShadersGenerated 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         UMaterialFunction

Notes

  • A .dsh is the natural home for VirtualFunction declarations. The editor's Create Virtual Function action writes one under DShader/VirtualFunctions.
  • Nothing stops a .dsf or .dsh from 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

MessageCauseFix
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

On this page