DreamShaderLang
Generation

Regeneration

What a rebuild destroys and what survives, the ownership guard, and the source-hash cache that decides whether a rebuild happens at all.

A generated asset is source-derived output, not a document. When a source file is compiled again, the generated graph is torn down and rebuilt from the .dsm or .dsf. Every hand edit inside the asset is destroyed, with exactly one exception.

Internalize that sentence and most of this page follows from it.

AspectValue
Applies toevery generated UMaterial, UDreamShaderMaterialInstance, UMaterialFunction, UMaterialFunctionMaterialLayer, UMaterialFunctionMaterialLayerBlend
Triggered byany compile that is not skipped by the source hash
Effectthe generated graph is torn down and rebuilt from source

What survives

EditSurvives regeneration
a comment box whose text does not begin with DreamShader: yes
a comment box whose text begins with DreamShader: no — deleted
nodes you added by handno — deleted
node property tweaks on generated nodesno — the node is deleted and recreated
node positionsno, unless pinned by a Layout section
material settings changed in the editorno — every property in Reset properties is restored to its default, then Settings is reapplied
parameter overrides on a generated ThinCustom instanceno — see the warning below
FunctionInput / FunctionOutput pin identities on a material functionyes since 1.3.2
named-reroute variable GUIDsyes — regenerated only when invalid

Sequence

  1. Modify() the target object.
  2. Clear the generated comments — every UMaterialExpressionComment whose text starts with the literal DreamShader: .
  3. Null every material property input, from the first to the last material-property slot.
  4. Delete every expression in the graph.
  5. Reset the material to defaults — see Reset properties. Material functions skip this step.
  6. Apply Settings.
  7. Rebuild: Properties nodes, the Graph body or the whole-surface Custom node, the Outputs bindings.
  8. Lay out — skipped in memory-only mode.
  9. Recompile.

Step 4 has two strategies. Below 1200 expressions each node is deleted individually through the material editing library, in up to 64 outer passes, reporting Deleting old Material node '{Name}'.... At 1200 or more the whole expression collection is un-rooted and marked as garbage in one pass. The material path also resets the material's editor parameter cache; the material-function path does not.

The whole-file parse, the Settings validation and the Outputs validation all run before the target asset is created or cleared, so a source file that fails any of them leaves the previously generated asset intact. A syntax error inside a Graph block is not one of those gates: the statement parser runs after step 4, so such a failure leaves the asset emptied.

The one hand edit that survives

A comment box whose text does not carry the DreamShader prefix.

AspectValue
PrefixDreamShader: — the word, a colon, and a single trailing space
Comparisoncase-sensitive
Effecta comment whose text starts with the prefix is deleted before the rebuild; every other comment is left untouched

dreamshader: Notes, DREAMSHADER: Notes and DreamShader:Notes (no space) all fail the prefix test and therefore survive. This is the supported way to annotate a generated material by hand.

The corollary: renaming a generated box from DreamShader: Sampling to Sampling makes it permanent, and the next regeneration creates a second box named DreamShader: Sampling on top of it. To keep DreamShader's own boxes in sync, leave their text alone and change the Comment(Name=…) entry in the source Layout section instead.

Preserved identities

Two identities are deliberately preserved so that existing call sites do not break.

  • Material function pins. Before the graph is cleared, the Id GUID of every UMaterialExpressionFunctionInput and UMaterialExpressionFunctionOutput is cached by name and restored onto the newly created pin with the same name. A MaterialFunctionCall node elsewhere in the project keeps its wiring across a regeneration of the function. since 1.3.2
  • Named reroutes. A declaration's variable GUID is regenerated only when the existing one is invalid.

Renaming an input or output in the source is therefore a breaking change for its call sites: the old name's GUID has nothing to restore onto.

Parameter overrides on a generated instance

Under the ThinCustom backend, regeneration calls ClearParameterValuesEditorOnly() on the emitted UDreamShaderMaterialInstance. Every parameter override set by hand on a generated instance is wiped on every regeneration — scalar, vector, texture, static switch and static component-mask alike. No diagnostic is emitted; the values are simply gone the next time the source is compiled.

Workaround: never tune a generated instance directly. Either

  • move the value into the source as a Properties default, so the generated instance carries it, or
  • create a child UMaterialInstanceConstant parented to the generated instance and override there. The child is a normal asset that regeneration never touches, and because the generated instance owns the static permutation, the child shares its shader map at no extra compile cost.

The Material Content Browser's instance-creation action produces exactly such a child, in <parent directory>/<Instance Subfolder> — see In-memory Materials.

The ownership guard

DreamShader refuses to overwrite an asset it did not generate.

AspectValue
Fires whenthe target package exists on disk and the existing object carries no DreamShader.SourceFile metadata
Applies toShader under the Graph backend, and ShaderFunction / ShaderLayer / ShaderLayerBlend
Resultgeneration fails; the existing asset is untouched
MessageRaised for
Asset '{ObjectPath}' already exists and was not generated by DreamShader. Rename your shader or move/delete the existing asset before regenerating.a material
Asset '{ObjectPath}' already exists and was not generated by DreamShader. Rename your function or move/delete the existing asset before regenerating.a material function

The guard is inert for packages that are not on disk: a memory-only asset has no saved package to protect, so the check does not run.

There is no ownership guard on the ThinCustom instance path. Generating a Shader under the default backend onto a path that holds a hand-authored UDreamShaderMaterialInstance checks only the class, not the provenance metadata — and then rebuilds it, clearing its parameter overrides. A hand-authored asset of any other class at that path is still rejected, with Asset '{ObjectPath}' already exists and is not a DreamShader instance material. Delete it (or remove Backend="Instance") before switching backends.

Workaround: do not hand-author UDreamShaderMaterialInstance assets. Create child instances as plain UMaterialInstanceConstant, which the guard's class check rejects outright.

Reset properties

Before the graph is rebuilt, a material's render state is restored to these values, in this order. Settings is applied afterwards, so any key you declare wins; anything you do not declare returns to the value below, regardless of what the material editor last held.

PropertyReset to
BlendModeBLEND_Opaque
MaterialDomainMD_Surface
shading modelMSM_DefaultLit
TwoSidedfalse
OpacityMaskClipValue0.3333
Wireframefalse
DitheredLODTransitionfalse
DitherOpacityMaskfalse
bAllowNegativeEmissiveColorfalse
bCastDynamicShadowAsMaskedfalse
bCastRayTracedShadowstrue
bEnableResponsiveAAfalse
bScreenSpaceReflectionsfalse
bContactShadowsfalse
bDisableDepthTestfalse
bOutputTranslucentVelocityfalse
bWriteOnlyAlphafalse
BlendableOutputAlphafalse
TranslucencyLightingModeTLM_VolumetricNonDirectional
bTangentSpaceNormaltrue
bAlwaysEvaluateWorldPositionOffsetfalse
bFullyRoughfalse
bIsSkyfalse
bIsThinSurfacefalse
MaterialDecalResponseMDR_ColorNormalRoughness
bHasPixelAnimation since UE 5.4false
NumCustomizedUVs0

Material functions have no render state; their asset-level fields are reapplied instead:

Source settingFieldWhen absent
DescriptionDescriptioncleared
UserExposedCaptionUserExposedCaptioncleared
ExposeToLibrarybExposeToLibraryset to false
LibraryCategoriesLibraryCategoriesText — comma-separated, entries trimmed, empties droppedcleared

The material-function usage is also re-stamped from the block kind on every regeneration.

The source-hash cache

Regeneration is expensive, so an unchanged source skips it entirely.

AspectValue
Hashed textthe prepared source — the file with every import recursively inlined
AlgorithmFCrc::StrCrc32, formatted %08x — eight lowercase hex digits
Stored inthe generated asset's package metadata, keyed by the asset object
Bypassed bythe bForce flag on the generation entry points
prepared text  ->  CRC32  ->  "%08x"  ->  DreamShader.SourceHash   e.g. "9f2c41ab"
source path    ->  project-relative, forward slashes  ->  DreamShader.SourceFile

What is hashed

The hash covers the text the parser actually sees, after import inlining — not the bytes of the file on disk. That is the single most surprising property of the cache.

ChangeChanges the hash of
edit M_Foo.dsmM_Foo.dsm
edit Common.dsh, imported by M_Foo.dsm and M_Bar.dsmboth M_Foo.dsm and M_Bar.dsm
move the project to another directorynothing — the stored path is project-relative
rename the source filethe stored path no longer matches, so nothing is skipped
reformat whitespace or edit a commentthe hash — the text is compared byte for byte, not semantically

Editing a .dsh invalidates every dependent .dsm and .dsf, but a header never generates anything by itself — saving it fails with DreamShader header '{File}' does not generate assets directly. Recompile dependent .dsm or .dsf files instead. The dependents are rebuilt only when they are themselves compiled: on their own save, through Generate all in-memory materials, through the Material Content Browser's Compile button, through the commandlet, or at cook.

Where the metadata lives

Two keys are written into the generated asset's package metadata, keyed by the asset object. These are the only two keys DreamShader ever sets — nothing writes a generation timestamp.

KeyValue
DreamShader.SourceFilethe source path made relative to the project directory, with forward slashes. A source outside the project keeps its absolute path.
DreamShader.SourceHashthe eight-hex-digit CRC32. Written only when non-empty.

Storing the project-relative path is deliberate: a checkout on another machine, or a moved project directory, still recognizes its own generated assets instead of regenerating everything. DreamShader.SourceFile doubles as the ownership marker — its presence is what the guard above tests, and what Clean Persisted Generated Assets filters on.

Which assets get stamped, and when:

AssetStamped
UDreamShaderMaterialInstance (ThinCustom)always — memory-only and persisted alike
the hidden MB_DreamThinBase_* basepersist mode only
UMaterial (Graph backend)persist mode only
UMaterialFunction / layer / layer blendpersist mode only

A ThinCustom instance additionally carries the source path and the hash as read-only UPROPERTYs — SourceFilePath and SourceHash, category DreamShader — so they are visible in the details panel without inspecting package metadata. SourceFilePath holds the full normalized source path, not the project-relative form the metadata stores.

When regeneration is skipped

The short circuit fires only when all of the following hold:

#Condition
1the generation call did not set bForce
2the asset exists and the newly computed hash is non-empty
3the stored DreamShader.SourceFile is present and non-empty
4the stored source path equals the project-relative path of the source being compiled, ignoring case
5the stored DreamShader.SourceHash equals the new hash, case-sensitively
AssetSkip pointExtra conditionMessage
ThinCustom materialafter the instance is created or reused, before the hidden base is createdSkipped {AssetPath} from {File}; source hash is unchanged.
Graph-backend materialafter the material is created or reusedSkipped {AssetPath} from {File}; source hash is unchanged.
Material functionafter the function asset is created or reusedthe asset's material-function usage must already match the one the block requiressilent — the asset path is returned with no message

Placing the ThinCustom check before the base is created is what makes a skip cheap: no base material, no ownership check, no graph teardown. A material function whose usage does not match — a ShaderLayer block whose asset is still marked Default, for instance — is regenerated even when the hash matches, and the usage is corrected.

Forcing a rebuild

PathForce
Auto-compile on saveno — the hash short circuit is active
Generate all in-memory materials (startup, backend-setting change)yes
Material Content Browser Compile / thumbnail refreshyes
Live preview renderyes
Materialize, and child-instance creationyes
Cookyes
Commandlet -run=DreamShaderonly with -Force; otherwise it reports Skipped {AssetPath} from {SourceFile}; source hash is unchanged.

There is no way to clear the stored hash from the source language. To force a rebuild without a force-capable entry point, either change the source text — any change, including whitespace — or delete the generated asset.

Notes

  • Regeneration is not undoable. Generated material instances are deliberately not RF_Transactional, because undo/redo desynchronizes the shader map.
  • The safest mental model: treat the .dsm / .dsf as the asset. Anything you want to persist belongs in the source.
  • A regeneration that is skipped by the source hash does none of the above — the asset is not touched at all.
  • Deleting the generated asset and recompiling is always equivalent to a forced regeneration, except that a material function's pin GUIDs are lost and its call sites break.
  • The hash is a CRC32, not a cryptographic digest. It detects edits; it is not an integrity mechanism.
  • The generated .ush helper include is not covered by this short circuit. It is rewritten on every compile of a unit that declares Function blocks.
  • The decompiler is the way to capture hand edits: export the edited material back to .dsm / .dsf, then make that the source of truth.

Diagnostics

MessageCauseFix
Asset '{ObjectPath}' already exists and was not generated by DreamShader. Rename your shader or move/delete the existing asset before regenerating.The ownership guard, on a material.Rename the Shader, or move or delete the hand-authored asset.
Asset '{ObjectPath}' already exists and was not generated by DreamShader. Rename your function or move/delete the existing asset before regenerating.The ownership guard, on a material function.
Asset '{ObjectPath}' already exists and is not a Material.Graph backend, a non-UMaterial at the path.Details
Asset '{ObjectPath}' already exists and is not a DreamShader instance material. Delete it (or remove Backend="Instance") before switching backends.ThinCustom backend, wrong class at the path.Details
Asset '{ObjectPath}' already exists and is not a MaterialFunction asset.A function kind, wrong class at the path.
Asset '{ObjectPath}' already exists as '{ActualClass}', but {Kind} generation requires '{ExpectedClass}'. Delete or move the existing asset and regenerate it.A function kind, wrong material-function subclass.
Generated DreamShader asset '{Path}' could not be saved.The package save failed after a successful rebuild.
Generated DreamShader asset packages could not be saved.The paired instance and base save failed.
In-memory material mode: '{PackageName}' already exists as a saved asset, which shadows in-memory regeneration. Delete the saved asset to make it fully in-memory.A log warning: a saved asset shadows a memory-only rebuild.Details
Skipped {AssetPath} from {File}; source hash is unchanged.Not an error — the short circuit fired.Pass -Force, or change the source text, to rebuild anyway.
DreamShader header '{File}' does not generate assets directly. Recompile dependent .dsm or .dsf files instead.A .dsh was compiled directly.

Worked example

Shader(Name="Docs/M_Regen")
{
    Properties {
        ScalarParameter Intensity = 2.0 [Group="Look"; SortPriority=10];
        VectorParameter Tint      = float4(1.0, 0.4, 0.1, 1.0) [Group="Look"];
    }
    Settings { Domain = "UI"; ShadingModel = "Unlit"; }
    Outputs  { vec3 Color; Base.EmissiveColor = Color; }
    Graph    { Color = Tint.rgb * Intensity; }
    Layout   { Node(Var="Color", X=-400, Y=0); }
}

Hand-edit the generated asset, then save the .dsm again:

before regeneration                              after regeneration
-----------------------------------------------  --------------------------------------------
comment "DreamShader: Output: EmissiveColor"     recreated
comment "Reviewed 2026-07-30"                    KEPT — no DreamShader: prefix
extra Multiply node wired in by hand             deleted
Two Sided ticked in the material editor          reset to false (not declared in Settings)
Intensity override = 5.0 on the instance         cleared, back to the source default 2.0
Color node dragged to (900, 400)                 back to (-400, 0), pinned by Layout

And the metadata on the generated instance:

DreamShader.SourceFile   DShader/Docs/M_Regen.dsm
DreamShader.SourceHash   9f2c41ab

Where next

On this page