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
- Edit a
.dsm,.dsfor.dshunderDShader/. - Fix what the editor extension flags locally — syntax, unknown identifiers, bad imports.
- Save. The source-directory watcher sees the change.
- 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. - DreamShader compiles the file and reports the result to the Output Log.
- 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.
| # | Stage | Notes |
|---|---|---|
| 1 | File-kind gate | a .dsh never generates — it fails here by design |
| 2 | Load prepared source | every import inlined recursively into one text |
| 3 | Content gate | a per-file substring scan of what that extension may declare |
| 4 | Parse | the whole prepared text, as one parse unit |
| 5 | Hash | CRC32 of the prepared text, formatted as eight hex digits |
| 6 | Write the helper include | only when the unit declares at least one Function |
| 7 | Material function assets | one per ShaderFunction / ShaderLayer / ShaderLayerBlend, in declaration order |
| 8 | The material | only 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
| Kind | You edit it to | Saving it |
|---|---|---|
.dsm | change a material | compiles the material and any function assets declared beside it |
.dsf | change a reusable function asset | compiles the function assets it declares |
.dsh | change shared helpers | generates nothing — see below |
What triggers a compile
| Trigger | Forced | Target | Scope |
|---|---|---|---|
| Auto-compile on save (watcher + debounce) | no | memory | the saved file — the common path |
| Generate all in-memory materials — editor startup, and every change to Default Compiler Backend | yes | memory | every project source |
| Material Content Browser Compile / Compile all / thumbnail refresh | yes | memory | one source, or all listed sources |
| Live preview render | yes | memory | one source |
| Materialize, and creating a child instance of a memory-only material | yes | disk | one source, persisted |
Commandlet -run=DreamShader | caller's choice | disk | persisted |
| Cook, on the cook director process only | yes | disk | every 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:
| Key | Value |
|---|---|
DreamShader.SourceFile | the source path relative to the project directory, with forward slashes |
DreamShader.SourceHash | the 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:
| Change | Effect |
|---|---|
edit M_Foo.dsm | invalidates M_Foo.dsm |
edit Common.dsh, imported by two materials | invalidates both materials |
| reformat whitespace, edit a comment | invalidates — the comparison is textual, not semantic |
| move the project to another directory | invalidates nothing — the stored path is project-relative |
| rename the source file | the 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
.dsmand.dsfinto 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.
| Surface | Shows |
|---|---|
| Output Log | every message, in order |
| Gen page | one error per file — the first accepted diagnostic only |
| Editor extension | the 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
- The Pipeline — every stage and every message it can emit
- Regeneration — what survives a rebuild, in full
- Editor Tools — the Tools menu, the browser tab, the clean commands
- VSCode and Rider — diagnostics, preview and package commands
- Commandlet — compiling headlessly