DreamShaderLang
Language

Source Files

The three 1.x file kinds, what each may declare, how that rule is enforced, and how a file is compiled together with the headers it imports.

Source files

DreamShader » DreamShaderLang » Source files

The three DreamShaderLang file kinds — .dsm, .dsf and .dsh — and the rule that decides which top-level blocks each may contain.

Declared in—
Kindtranslation unit
Extensions.dsm material · .dsf function (since 1.3.5) · .dsh header
Discovered under<Project>/DShader by default, plus every enabled plugin's DShader; configurable
Enforced bythe legacy front end (what a .dsh may hold, one Shader per file) and the binder (one kind of product per file) (since 2.0.0)

Synopsis

// <name>.dsm — Dream Shader Material
[ import "<header>.dsh" ; ]…
[ Shader( Name = "…" [, Root = "…"] ) { … } ]          // at most one per file
[ { VirtualFunction | Namespace | Function | GraphFunction } … ]…
// <name>.dsf — Dream Shader Function
[ import "<header>.dsh" ; ]…
[ { ShaderFunction | ShaderLayer | ShaderLayerBlend }( Name = "…" [, Root = "…"] ) { … } ]…
[ { VirtualFunction | Namespace | Function | GraphFunction } … ]…
// <name>.dsh — Dream Shader Header
[ import "<header>.dsh" ; ]…
[ { VirtualFunction | Namespace | Function | GraphFunction } … ]…     // and 2.0 declarations, side by side

What each kind may contain

Top-level block.dsm.dsf.dshReference
Shaderoneno — DSH2259noShader
ShaderFunctionyesyesnoShaderFunction
ShaderLayeryesyesnoShaderLayer
ShaderLayerBlendyesyesnoShaderLayerBlend
MaterialLayer (deprecated in 1.3.0)yesyesnoShaderLayer
MaterialLayerBlend (deprecated in 1.3.0)yesyesnoShaderLayer
VirtualFunctionyesyesyesVirtualFunction
NamespaceyesyesyesNamespace
FunctionyesyesyesFunction
GraphFunctionyesyesyesGraphFunction
import of a .dshyesyesyesimport
2.0 declarations (uniform, export, #pragma, …)nonoyesDreamShaderLang 2.0

A .dsm builds its Shader and every ShaderFunction or layer block beside it, each into its own asset, as 1.x did; the material may call such a function by name (2.0.0 – 2.1.0 refused it with DSH6201). A .dss makes a material or function assets, not both (DSH6201), so dsc migrate cannot rewrite such a file as one .dss: give each function a .dsf of its own first.

How the restriction is enforced

(since 2.0.0) By the parser, one declaration at a time, and by the binder over the declarations of one file.

RuleChecked byCode
a .dsh holds no asset block (Shader, ShaderFunction, ShaderLayer, ShaderLayerBlend or an old spelling of a layer)the legacy front end, at the block wordDSH2249
a .dsm / .dsf holds 1.x blocks only — a uniform, an export, a #pragma is 2.0 syntaxthe legacy front endDSH2248
a .dss holds 2.0 declarations only — a 1.x block word therethe 2.0 parserDSH3222
one Shader block per filethe legacy front endDSH2250
a .dsf holds no Shader blockthe legacy front end, at the block wordDSH2259
a .dsm / .dsf declares at least one blockthe legacy front endDSH2254
an import names a .dshthe legacy front endDSH2252

A .dsh may hold 1.x blocks and 2.0 declarations side by side, so a project can migrate one file at a time. A comment or a string that mentions Shader( is just a comment or a string.

[!NOTE] Through 1.9.x the rule was a case-insensitive substring scan of each file's text, after its import lines were removed: a .dsh was refused for containing Shader(, ShaderFunction(, … — in a comment or a string too — and a .dsf for containing Shader(, while Shader ( with a space slipped past. Nothing scans text any more: the front end reads the block word, so a .dsf with a Shader block is DSH2259 however it is spaced (2.0.0 – 2.1.0 did not refuse it, and the file built a material like a .dsm). See the retired parser page.

Source roots

Discovery runs over a list of source roots, not a single directory.

RootDirectoryWritableContributed when
ProjectSource Directory, <Project>/DShader by defaultyesalways
(one per plugin)<Plugin>/DShadernothe plugin is enabled, the folder exists, and Scan Plugin Source Directories is on

Each root owns a Packages folder of its own at <Root>/Packages. A root is the unit of import resolution: an import specifier is resolved against the bases of the root that owns the importing file and never against another root's — see import.

Only the project root is writable. Plugin roots are discovered, parsed and compiled like any other source, but the editor never rewrites a file under one: VirtualFunction sync skips them, and every editor action that writes a source file targets the project root.

[!NOTE] A plugin DShader folder that overlaps an existing root is ignored, with a warning naming both. This happens when Source Directory is pointed at a plugin folder, or at a directory containing one; without the check the same file would belong to two roots and its imports would resolve by scan order.

The source-directory watcher registers one watch per root, so Auto Compile On Save fires for a plugin's sources exactly as it does for the project's.

[!NOTE] The root list is cached. A DShader folder created while the editor is open appears when Refresh (F5) is pressed in the Material Content Browser; its watch, registered once at startup, attaches on the next editor start, so that root's Auto Compile On Save begins working then.

A file under a plugin root that declares no Root= defaults to that plugin's mount point rather than to /Game — a .dsm in Plugins/MoonToon/DShader generates into /MoonToon with no attribute at all. Write Root="/" to opt back into /Game. See Asset paths.

File discovery

Files are found by recursive scans of every source root. Directories are not returned, and every result is normalized to a full path.

ScanExtensionsExcludedUsed for
All source files.dsm, .dsf, .dsh; on the 2.0 line also .dss, .dsi and (since 2.1.0) .dspeverything under any root's packages directorydependency graph, workspace generation, editor file lists (result sorted)
Generatable files.dsm, .dsf; on the 2.0 line also .dss, .dsi and (since 2.1.0) .dsp — never .dsh.dsm files under any root's packages directorybatch compile / generate-all

[!NOTE] The two exclusions are not symmetric. The all-sources scan drops every file under a 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 — and on the 2.0 line so is a .dss, a .dsi or a .dsp there.

(since 2.0.0) The command-line verbs that name their files with -All use scans of their own:

-All ofExtensionsExcluded
compilethe all-sources scan without .dsh, every .dsf firsteverything under any root's packages directory
check, dump-ir, dump-layout, index, list-generated.dss, .dsi, .dsp, .dsm, .dsfeverything under any root's packages directory
fmt.dss, .dsi, .dsp, .dsheverything under any root's packages directory, and every root that is not writable — a plugin's sources are formatted only when named

The 2.0 kinds are described under DreamShaderLang 2.0: .dsi material instances and (UE 5.8) .dsp Custom Pass pipelines.

DirectoryDefaultProject setting
Source<Project>/DShaderSource Directory
Packages<Source>/Packagesderived from Source Directory; not separately configurable
Plugin source<Plugin>/DShaderScan Plugin Source Directories (on/off only; the path is fixed)
Plugin packages<Plugin>/DShader/Packagesderived; not separately configurable
Generated shaders<Project>/Intermediate/DreamShader/GeneratedShadersGenerated Shader Directory

A configured path that is relative is resolved against the project directory. The project's source, packages and generated-shader directories are created at module startup; a plugin's DShader folder is never created, only discovered. See Project settings and Packages.

What each kind generates

KindCompiled asProduces
.dsmmaterial entry pointthe UMaterial (or ThinCustom instance) of its Shader block
.dsfasset entry pointthe function assets it declares
.dshnot an entry pointnothing; a header is read through the import of the file that names it, and compiling one on its own is DSH8296

A .dsm with no Shader block is still compilable — it produces whatever function assets it does declare. A compile builds what its own file declares, and never what a header it imports declares: a header holds no asset block.

Notes

  • Extensions are compared case-insensitively. M_Water.DSM is a material file.
  • At most one Shader per file (DSH2250). Through 1.9.x imports were inlined into one text before parsing, so the limit spanned the whole import closure; since 2.0.0 a header is read on its own and cannot hold a Shader at all, so an import never adds one.
  • A .dsh is the natural home for VirtualFunction declarations; the editor's Create Virtual Function action writes one under DShader/VirtualFunctions. See VirtualFunction tools.
  • Only a .dsh can be imported (since 2.0.0). An import of a .dsf or a .dsm is DSH2252: a material or function file is compiled on its own, and its assets are reached from another file through a VirtualFunction.

Diagnostics

CodeRaised when
DSH2249a .dsh holds an asset block
DSH2248a .dsm / .dsf holds 2.0 syntax
DSH3222a .dss holds a 1.x block
DSH2250a second Shader block in one file
DSH2259a .dsf holds a Shader block
DSH2254a .dsm / .dsf declares no block at all
DSH2252an import the include resolver does not read — see import
DSH8296a file that is not a source the compiler builds — a .dsh, or another extension — was asked to compile

The codes of an import that cannot be resolved, read or parsed, and of a cycle, are on import. Every code: diagnostics.

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: declares ShaderFunction blocks
import "Shared/Common.dsh";

ShaderFunction(Name="Functions/F_Tint")
{
    Inputs = {
        vec3 InColor;
        opt float Strength = 1.0;
    }

    Outputs = {
        vec3 OutColor;
    }

    Graph = {
        OutColor = Common::ApplyTint(InColor, vec3(1.0, 0.9, 0.8)) * Strength;
    }
}
// DShader/Materials/M_Water.dsm — material file: one Shader block
import "Shared/Common.dsh";

Shader(Name="Materials/M_Water")
{
    Outputs = {
        vec3 Color;
        Base.EmissiveColor = Color;
    }

    Graph = {
        Color = Common::ApplyTint(vec3(0.1, 0.3, 0.6), vec3(1.0, 0.9, 0.8));
    }
}

Each source builds its own asset; the header builds nothing and is read by both:

M_Water.dsm   ->  /Game/Materials/M_Water        UMaterial
F_Tint.dsf    ->  /Game/Functions/F_Tint         UMaterialFunction
Common.dsh    ->  (nothing)

See also

On this page