DreamShaderLang
Getting Started

Daily Workflow

The save, debounce and generate loop — what triggers a rebuild, what is skipped, and what editing a .dsh invalidates.

The working loop is: edit a source file, save it, let the plugin compile, look at the result. The editor extensions improve the editing half; the Unreal plugin owns generation, and generation happens only inside the editor process.

The loop

  1. Edit a .dsm, .dsf or .dsh under DShader/.
  2. Fix what the editor extension flags locally — syntax, unknown identifiers, bad imports.
  3. Save. The source-directory watcher sees the change.
  4. The debounce timer waits out Save Debounce Seconds — default 0.25, clamped to [0.05, 10.0] — so a burst of saves is one compile.
  5. DreamShader compiles the file and reports the result to the Output Log.
  6. Inspect the material through Tools ▸ DreamShader ▸ Material Content Browser, or through the editor extension's preview.

Auto-compile is governed by two project settings: Auto Compile On Save (default on) and Save Debounce Seconds. With auto-compile off, the watcher ignores file changes entirely and you compile by hand.

What one compile does

Every stage is a gate: the first failure aborts the compile, and its message becomes the compile result.

#StageNotes
1File-kind gatea .dsh never generates — it fails here by design
2Load prepared sourceevery import inlined recursively into one text
3Content gatea per-file substring scan of what that extension may declare
4Parsethe whole prepared text, as one parse unit
5HashCRC32 of the prepared text, formatted as eight hex digits
6Write the helper includeonly when the unit declares at least one Function
7Material function assetsone per ShaderFunction / ShaderLayer / ShaderLayerBlend, in declaration order
8The materialonly when the unit declares a top-level Shader

Function assets are generated before the material, so a Shader can call a ShaderFunction declared beside it in the same file. Parser warnings never fail a compile; they are appended to the result under a Warnings: header.

Success looks like one of:

Generated DreamShader thin-custom material /Game/Materials/M_Emissive from .../M_Emissive.dsm.
Generated ShaderFunction /Game/Functions/F_Tint from .../M_Emissive.dsm.
Generated DreamShader helper include '...' from .../Common.dsm.
Skipped /Game/Materials/M_Emissive from .../M_Emissive.dsm; source hash is unchanged.

A file can also compile successfully and produce nothing placeable: a unit of only Function, GraphFunction or VirtualFunction declarations succeeds with a message saying so. A unit that declares no top-level block at all is a failure.

The three file kinds in practice

KindYou edit it toSaving it
.dsmchange a materialcompiles the material and any function assets declared beside it
.dsfchange a reusable function assetcompiles the function assets it declares
.dshchange shared helpersgenerates nothing — see below

What triggers a compile

TriggerForcedTargetScope
Auto-compile on save (watcher + debounce)nomemorythe saved file — the common path
Generate all in-memory materials — editor startup, and every change to Default Compiler Backendyesmemoryevery project source
Material Content Browser Compile / Compile all / thumbnail refreshyesmemoryone source, or all listed sources
Live preview renderyesmemoryone source
Materialize, and creating a child instance of a memory-only materialyesdiskone source, persisted
Commandlet -run=DreamShadercaller's choicediskpersisted
Cook, on the cook director process onlyyesdiskevery project source, persisted

The interactive editor never writes a per-material .uasset. Generated assets live in memory until a cook, the commandlet, or an explicit Materialize puts them on disk. See In-memory Materials.

When a rebuild is skipped

Each generated asset stores two keys in its package metadata:

KeyValue
DreamShader.SourceFilethe source path relative to the project directory, with forward slashes
DreamShader.SourceHashthe eight-hex-digit CRC32 of the prepared source text

On a non-forced compile the work is skipped when the asset exists, the stored source path matches (ignoring case), and the stored hash matches (case-sensitively). The result reads Skipped {AssetPath} from {File}; source hash is unchanged.

Because the hash is taken after import inlining and compared byte for byte:

ChangeEffect
edit M_Foo.dsminvalidates M_Foo.dsm
edit Common.dsh, imported by two materialsinvalidates both materials
reformat whitespace, edit a commentinvalidates — the comparison is textual, not semantic
move the project to another directoryinvalidates nothing — the stored path is project-relative
rename the source filethe stored path stops matching, so nothing is skipped

There is no way to clear the stored hash from the language. To force a rebuild through a non-forcing entry point, change the source text — any change — or delete the generated asset.

Editing a .dsh

Saving a header generates nothing. It fails with DreamShader header '{File}' does not generate assets directly. Recompile dependent .dsm or .dsf files instead. Editing a .dsh invalidates the hash of every .dsm and .dsf that imports it, but those are only rebuilt when they are themselves compiled.

The routes that rebuild the dependents:

  • saving each dependent .dsm / .dsf;
  • Tools ▸ DreamShader ▸ Recompile DSM, which stamps every project .dsm and .dsf into the pending queue;
  • Generate all in-memory materials — editor startup, and every change to Default Compiler Backend;
  • the Material Content Browser's Compile / Compile all;
  • the commandlet, or a cook.

The Gen page shows a header as ◆ function / header with a used by {N} material(s) sub-label, so you can see the blast radius before editing one.

What a rebuild destroys

A generated asset is source-derived output, not a document. Regeneration clears the generated comments, nulls every material property input, deletes every expression, resets the material's render state to engine defaults, then reapplies Settings and rebuilds. Everything you changed by hand inside the asset is gone, with exactly one exception: a comment box whose text does not begin with the literal DreamShader: survives.

Under the default backend, regeneration also clears every parameter override set by hand on a generated material instance — scalar, vector, texture, static switch alike — with no diagnostic. Put the value in the source as a Properties default, or override in a child UMaterialInstanceConstant, which regeneration never touches. See Regeneration.

DreamShader also refuses to overwrite an asset it did not generate: a saved asset with no DreamShader.SourceFile metadata fails with Asset '…' already exists and was not generated by DreamShader. That guard does not cover the ThinCustom instance path, so do not hand-author UDreamShaderMaterialInstance assets.

When something fails

Parse and generation errors go to the Output Log under LogDreamShader, to the Material Content Browser's source list, and to Saved/DreamShader/Bridge/diagnostics.json, which is what the editor extensions read.

SurfaceShows
Output Logevery message, in order
Gen pageone error per file — the first accepted diagnostic only
Editor extensionthe full per-file list, at the reported line and column

Line and column numbers refer to the file you actually edited: import lines are replaced by blank lines and each inlined file is bracketed with source markers, so positions stay put. Look the message up in the diagnostics index.

Migrating an existing material

Right-click a UMaterial or UMaterialFunction in the Content Browser and use DreamShader ▸ Export DSM / Export DSF. The output lands under DShader/Decompiled/ and opens in your editor. Treat it as a starting point — review names, helper extraction and imports before making it the source of truth. See Decompiler.

Next

On this page