DreamShaderLang
Tooling

Commandlet

-run=DreamShader — every command and switch, path resolution, exit codes, and why it writes real assets when the editor does not.

Commandlet

DreamShader » Tools » Commandlet

-run=DreamShader — a headless entry point that compiles DreamShader sources into persistent assets and decompiles existing material assets back into source files.

KindUCommandlet subclass — UDreamShaderCommandlet, in the DreamShaderEditor module
Invocation-run=DreamShader (equivalently -run=DreamShaderCommandlet)
Commandscompile, generate, decompile, export, dump-graph (since 1.9.0); migrate, check, dump-ir, dump-layout, index, export-catalog, fmt, list-generated (2.0); pass-registry (2.1.0)
Exit codes0 success, 1 failure
Log categoryLogDreamShader

Synopsis

UnrealEditor-Cmd.exe <project>.uproject -run=DreamShader <command> [<option>…]

<command> ::= { compile | generate | decompile | export | migrate | dump-graph
              | check | dump-ir | dump-layout | index | export-catalog | fmt | list-generated
              | pass-registry }

-run=DreamShader { compile | generate } { -Source=<path> | -File=<path> | -All } [-Force]
                                        [-Define=<NAME>[=<value>]]…
-run=DreamShader { decompile | export } { -Asset=<object-path> | -SourceFile=<path> }
                                        [{ -Out | -Output }=<path>] [-Format={ Dss | Legacy | Auto }]
                                        [-KeepAssetPath] [-Readable] [-DiagnosticsOut=<file>]
-run=DreamShader migrate { -Source=<path> | -All | -Root=<source root> }
                                        [-Check] [-DryRun] [-Out=<dir>] [-NoBackup]
-run=DreamShader dump-graph { -Source=<path> | -File=<path> | -All } [{ -Out | -Output }=<dir>]
                                        [-Define=<NAME>[=<value>]]…
-run=DreamShader check { -Source=<path> | -All } [-Shaders] [-Platform=<list>] [-Quality=<list>]
                                        [-Timeout=<seconds>] [-DiagnosticsOut=<file>]
-run=DreamShader dump-ir { -Source=<path> | -All } [-Out=<dir>] [-Json]
-run=DreamShader dump-layout { -Source=<path> | -All } [-Style={ Blocks | SourceBands | Layered | All }]
                                        [-Out=<dir>] [-Json]
-run=DreamShader index { -Source=<path> | -All } [-Out=<dir>]
-run=DreamShader export-catalog [-Out=<file>]
-run=DreamShader fmt { -Source=<path> | -All } [-Check] [-Out=<dir>]
-run=DreamShader list-generated { -Source=<path> | -All } [-As={ Packages | Files | GitIgnore | Json }]
                                        [-Out=<file>] [-IncludeEphemeral]
-run=DreamShader pass-registry [-Gc | -Rebuild]

compile, dump-graph, check, dump-ir, dump-layout, index and list-generated take every compilable source: .dss, .dsi, .dsp, .dsm and .dsf. There is one compiler; a 1.x source is read by the legacy front end and a .dsh header is compiled through the sources that include it. fmt takes 2.0 text — .dss, .dsi, .dsp, and a .dsh that has no 1.x declarations left. What each verb does with a Custom Pass pipeline is in .dsp sources.

Commandlet flags declared by the class: IsClient = false, IsEditor = true, IsServer = false, LogToConsole = true.

Commands

All commands accept optional -AssetWritesManifest=<unused-json-path> together with -AssetWritesRunId=<run-id>. These record successful .uasset saves performed by this commandlet, including their absolute filenames and MD5 content fingerprints, in a versioned JSON manifest. The output path must not already exist; failure to write the requested manifest exits 1. The PowerShell driver uses this provenance and a pre-run snapshot for -CleanNew, retaining unrelated assets and files modified after the recorded save. The manifest does not authorize deletion of pre-existing assets or files outside the driver's Content roots.

The command name is the first bare (non--) argument. If there is no bare argument, a Command=<name> parameter is consulted instead. Matching is case-insensitive; surrounding whitespace is trimmed.

SpellingEquivalent toEffect
compile—Compile one source file or every project source into assets
generatecompileIdentical; alternate spelling
decompile—Export a material, function, layer, blend or material instance to a source file: 2.0 text by default (.dss, or .dsi for an instance), 1.x text with -Format=Legacy or an -Out ending in .dsm / .dsf — see Decompiler. A Custom Pass pipeline is written as a .dsp, and has no 1.x text (2.1.0)
exportdecompileIdentical; alternate spelling
migrate (2.0)—Rewrite 1.x sources as .dss, proving each rewrite first — see Migrate
check (2.0)—Compile as far as IR validation and write no asset; -Shaders builds the products and reports HLSL errors against source lines
dump-ir (2.0)—Write the lowered graph IR of a source as text, and as JSON with -Json
dump-layout (2.0)—Draw the 2.0 graph layout of every product of a source as SVG, building nothing — see dump-layout
index (2.0)—Write the symbol index a language service reads. The editor writes the same file after every compile.
export-catalog (2.0)—Write the builtin node catalog as JSON, so tools can bind UE.* without an editor. The editor writes the same file once it has loaded.
fmt (2.0)formatRewrite 2.0 sources in the printer's layout; -Check reports and writes nothing — see fmt
list-generated (2.0)—Name every asset the sources build, building none — see list-generated
pass-registry (2.1.0)passregistryList the Custom Pass HLSL slots and what each one is; -Gc frees the dead ones, -Rebuild rebuilds the registry — see pass-registry
dump-graph (since 1.9.0)—Write a canonical JSON fingerprint of the graph each source generates
dumpgraphdump-graphIdentical; the hyphen is optional

Anything else fails with Unknown DreamShader command '{Command}'. followed by the usage banner, and exits 1.

[!WARNING] The first bare token is taken as the command name unconditionally, before any of it is validated. Writing an option without its leading dash first — -run=DreamShader Source=X compile — consumes Source=X as the command name and produces Unknown DreamShader command 'Source=X'. Keep the command as the first bare argument.

compile / generate

OptionAliasesTypeRequiredDefaultMeaning
-Source=<path>-File=<path>stringone of -Source / -File / -All—Compile exactly one source file
-All—flagone of -Source / -File / -AlloffCompile every project DreamShader source
-Force—flagnooffBypass the source-hash skip and regenerate unconditionally
-Define=<NAME>[=<value>] (since 1.9.0)-D=<NAME>[=<value>]string, repeatableno—Contribute one preprocessor define to this run

Precedence: -Source is looked up first, then -File; -All is consulted only when neither yielded a value. -Source and -All together silently compiles only the one file. With none of the three, the usage banner is logged at Error and the run exits 1.

(after 2.1.2) -Source / -File names the file, so its NoGeneration assets are built too; -All names none, and leaves them as they are on disk. check -Shaders follows the same rule.

-Source path resolution

Tried in this order; the first that applies wins.

OrderConditionResult
1value is empty, or is an absolute pathnormalized as given
2<SourceDirectory>/<value> existsthat path
3<ProjectDir>/<value> existsthat path
4otherwisenormalized as given — which then fails the extension guard or the compile

<SourceDirectory> is the Source Directory project setting, default <Project>/DShader. A relative value is therefore resolved against the DreamShader source tree first and the project directory second.

-All discovery and ordering

StepRule
1Recursively collect *.dsm, *.dsh, *.dsf, *.dss, *.dsi and *.dsp under every source root — <SourceDirectory>, and the DShader folder of every plugin that has one
2Drop everything under each root's Packages folder except what a package builds: its Assets sources and its asset headers (after 2.1.2; before, everything) — see Packages
3Drop .dsh headers — they generate no assets and are compiled through the sources that include them — except a package's asset header
4Sort: .dsf function files and asset headers first (rank 0), then every other kind (rank 1); ties broken by case-insensitive path comparison
5Compile in passes: a source that fails is held back while its pass still built something else, and retried in the next pass; once a pass builds nothing new, what still fails is reported (since 2.1.2)

A material that calls a function another source generates loads that function's asset, so the asset must exist first. The sort covers the common case in one pass — a .dsm calling a .dsf — but not a function calling another function, nor a .dss whose exported function another source calls; step 5 covers every shape, on a clean workspace too. A source that keeps failing — a real error, a dependency no source generates, or a cycle — costs at most one extra attempt per pass that built something, and is reported with the message of its last attempt, after every success. The cook runs the same steps (Cook behaviour). A .dsp needs no rank of its own (2.1.0): a material one of its passes names that is missing or older than its source is compiled first by the .dsp's own compile.

-Define (since 1.9.0)

Each occurrence contributes one entry to the define table this run compiles against. The option is repeatable — later occurrences add names rather than replacing the set.

Written asDefines
-Define=FOO=1FOO to 1
-Define=PROFILE=cinematicPROFILE to the string cinematic
-Define="NOTE=two words"NOTE to two words — quote the whole argument, not just the value
-Define=CI_BUILDCI_BUILD to an empty value: a bare marker, for which defined(CI_BUILD) is 1 and #if CI_BUILD is true
-D=FOO=1the same as -Define=FOO=1 — -D is the short spelling

The name is everything up to the first = in the option's value, so a value may itself contain =. Names are case-sensitive.

[!WARNING] -Define is one of the few options that must be read off the raw command line rather than through the commandlet's usual parameter map, and the reason is a trap in the engine rather than a preference. UCommandlet::ParseCommandLine's four-argument overload does not put -Foo=Bar into both lists: it removes it from Switches and moves it into Params. So scanning Switches finds no -Define at all, and Params is keyed by option name, which collapses -Define=A=1 -Define=B=2 into a single entry. Either route silently drops every define but one.

The command line is the highest-precedence define tier, so it overrides Preprocessor Defines in the project settings and anything a module registered in C++ — with one exception: a name beginning with DS_ is reserved for the builtins and is dropped with a warning, because a run that lies about the engine version or about Substrate does not produce a different material, it produces a graph the engine cannot compile at all.

[!NOTE] Defines are part of the build key, so a run with a different -Define set rebuilds the sources that read a changed name without -Force, and still skips the ones that do not read it. That is what makes a per-configuration CI sweep affordable: one run per define set, each rebuilding only what its own switches touch.

Per-file guard

Each file in the compile list is checked before compiling: a path that is not a DreamShader source, or that is a .dsh header, logs DreamShader compile requires a .dss, .dsi, .dsp, .dsm or .dsf file: {Path}, marks the whole run failed, and the loop continues with the remaining files. One bad file therefore does not prevent the rest from compiling, but the process still exits 1.

Result messages

Each file's compile result is logged verbatim — at Display on success, at Error on failure. When one file produces several assets, the messages are joined with newlines.

MessageOutcome
Generated {Kind} {AssetPath} from {SourceFile}.one product built. {Kind} is Material, MaterialFunction, MaterialLayer, MaterialLayerBlend, MaterialInstance or PassPipeline (2.1.0) — for every source language and both backends; a ThinCustom material is Material with the instance's path (since 2.0.0)
Generated RenderTarget {ObjectPath} from {SourceFile}.the render target of an exported buffer, made or reused by its pipeline's build and saved with it (2.1.0); follows its pipeline's line
Skipped {AssetPath} from {SourceFile}; source hash is unchanged (build key {BuildKey}).hash match — pass -Force to regenerate
Skipped {AssetPath} from {SourceFile}; the source marks it NoGeneration, and only a compile that names the source builds it.(after 2.1.2) a NoGeneration asset, left as it is — compile with -Source to build it
Skipped {AssetPath} from {SourceFile}; the source marks it NoGeneration, and it has never been built.(after 2.1.2) the same, with no asset on disk
Compiled {SourceFile}; it declares no material and no exported function, so no asset was written.success, nothing to write — a file of helpers, VirtualFunction or GraphFunction declarations only (since 2.0.0)
Warnings: followed by one diagnostic per lineappended to a success message when the compile raised warnings
one diagnostic per line, <file>(<line>,<col>): DSHnnnn: <message>failure: the first error first, then the other errors, warnings and infos. See Diagnostics

The 1.x result lines — a separate thin-custom wording, a helper-include line, the "no assets to generate" and .dsh failures, the (virtual) suffix — are not written since 2.0.0. A file with no product succeeds; a .dsh never reaches the compiler, because the per-file guard above stops it.

decompile / export

OptionAliasesTypeRequiredDefaultMeaning
-Asset=<object-path>—stringone of the two—The asset to decompile
-SourceFile=<path>—stringone of the two—Decompile every asset that source builds into one file
-Out=<path>-Output=<path>stringnocomputedDestination file
-Format=<Dss|Legacy|Auto>—enumnoAutoAuto lets the extension of -Out decide: .dsm / .dsf is 1.x text, anything else 2.0. A format that contradicts the extension is DSH9085.
-KeepAssetPath—flagnooffWrite /// @name with the asset's own path when the output file would otherwise name another
-Readable—flagnooffPrefer HLSL sugar over class-exact node calls (a rebuilt graph may then differ in node classes), and read the Substrate sugar back (A * w, Transmittance = …)
-DiagnosticsOut=<file>—stringno—The decompile's diagnostics as JSON (schema dreamshader-diagnostics)

Asset path normalization

StepRule
1Every \ becomes /
2If the path starts with / and contains no ., the short name is appended: /Game/Path/Asset → /Game/Path/Asset.Asset

The normalized path is loaded first. If that fails and normalization actually changed the string, the raw (quote-stripped) input is retried unchanged.

Supported asset classes

ClassEmitsDefault destination
UMaterial.dss; .dsm with -Format=Legacy<SourceDirectory>/Decompiled/Materials/<package path>.dss (.dsm)
UMaterialFunction.dss; .dsf with -Format=Legacy<SourceDirectory>/Decompiled/Functions/<package path>.dss (.dsf)
UMaterialFunctionMaterialLayer.dss; .dsf with -Format=Legacy<SourceDirectory>/Decompiled/Layers/<package path>.dss (.dsf)
UMaterialFunctionMaterialLayerBlend.dss; .dsf with -Format=Legacy<SourceDirectory>/Decompiled/LayerBlends/<package path>.dss (.dsf)
UMaterialInstanceConstant (2.0).dsi — 2.0 text only. A generated ThinCustom instance is the material it stands for, and comes back as a .dss under Materials/<SourceDirectory>/Decompiled/Instances/<package path>.dsi
UDreamPassPipeline (2.1.0).dsp — 2.0 text only: -Format=Legacy, or an -Out ending in .dsm / .dsf, is refused, and an -Out with any extension but .dsp is DSH9210<SourceDirectory>/Decompiled/Pipelines/<package path>.dsp
anything else—error

Path segments are sanitized: control characters and < > : " / \ | ? * become _; an empty folder segment becomes Folder<N> and an empty asset segment becomes Asset<N>. Output is written UTF-8 without a BOM. -Out bypasses the computed destination entirely — the directory is created if needed. Full decompiler behaviour is on Decompiler.

dump-graph (since 1.9.0)

A developer tool, not part of a normal build. It generates each source the way compile does and then, instead of saving an asset, writes one canonical JSON file describing the graph that generation produced: node classes, their reflected properties, every connection, and pin order.

The reason it exists is parity. A JSON capture taken before a compiler change is the only ground truth a rewritten compiler can be held to, source file by source file — the machine-readable version of "decompile both and diff the text", without the decompiler's own opinions in the middle. Two captures are compared with an ordinary text diff.

OptionAliasesTypeRequiredDefaultMeaning
-Source=<path>-File=<path>stringone of -Source / -File / -All—Dump exactly one source file
-All—flagone of -Source / -File / -AlloffDump every project DreamShader source
-Out=<dir>-Output=<dir>stringno<Project>/Saved/DreamShader/GraphBaselineRoot of the dump tree
-Force—flagnooffAccepted and ignored; a dump always regenerates
-Define=<NAME>[=<value>]-D=<NAME>[=<value>]string, repeatableno—Same define tier as compile; a dump is per define set

-Source resolution, -All discovery and the .dsf-before-.dsm ordering are literally the same code as compile. That is deliberate: a baseline that covered a different set of sources than the compiler does would report a missing file as a difference.

It never writes an asset

A baseline capture over a project must not be a rebuild-and-save of every generated .uasset just to read them back. dump-graph therefore runs with the compiler's write ownership switched off for the whole sweep, which makes generation refuse — before the old graph is torn down — any asset that would persist.

[!WARNING] The consequence is that an asset which already exists on disk is dumped as it stands, not regenerated. On a compiled tree those are the same graph. On a stale one they are not, silently. Run compile -All -Force first whenever the sources may have moved since the assets were written. The run counts these and says so: {N} of them already exist on disk, so dump-graph read them as they stand instead of rebuilding them…

Sources whose asset has no file behind it — the whole test corpus, and anything not yet compiled — are generated in memory and dumped from that, so nothing on disk is touched either way.

Output layout

<Out>/<root>/<source path relative to that root>.<asset leaf>.graph.json

<root> is the source root that owns the file — Project, or the owning plugin's name; DreamShaderPlugin for the plugin's own test corpus, and External for a file under none of them (whose relative path degrades to its file name). The source's extension is kept in the middle of the name on purpose: M_Foo.dsm and M_Foo.dsf would otherwise collide. Path separators inside the relative path stay separators, so the dump tree mirrors the source tree; the characters a file name cannot hold become _.

One file per asset, not per source: a .dsf declaring three ShaderFunction blocks writes three.

What the JSON contains

KeyMeaning
schemaDump format version — currently 1. Two captures are only comparable at the same number
pluginThe plugin's VersionName
source{ root, path } — the source root's name and the path relative to it
assetThe generated asset's object path
kindMaterial / MaterialFunction / MaterialLayer / MaterialLayerBlend / ThinCustomInstance / MaterialInstance / PassPipeline (2.1.0)
backendGraph, or ThinCustom for an instance material; absent for a material instance and a pass pipeline
nodesEvery expression in the graph, in canonical order (below)
properties(materials) connected material property → { from, output, mask }, keyed by EMaterialProperty enumerator name
settingsThe curated Settings set — Domain, ShadingModel, BlendMode, the usage/render bools and MaterialDecalResponse for a material; Usage, Description, bExposeToLibrary, LibraryCategories for a function
inputs / outputs(functions) name, type, sort priority, description, optional flag, preview value
instance(ThinCustom) parent.kind and parameters — the overrides, static switches and static component masks

For a ThinCustom instance the nodes, properties and settings describe the hidden base material the instance parents to, which is where the graph actually lives. The base's own object path is deliberately absent: it is a transient object in the editor and a subobject of the instance on disk, so the string differs between two captures of an identical graph.

A pipeline's dump

(since 2.1.0) A .dsp product has no graph, so its dump is the UDreamPassPipeline as the runtime reads it, every enumeration spelled the way the .dsp spells it. The run's line says nodes but counts the passes. The render targets of its exported buffers get no file of their own.

KeyMeaning
pipelineorder, defaultInjection, views, requires, enabled (the bool parameter, or null)
parametersin declaration order: name, default (type and value), group, description, slider ([min, max] or null), sortPriority
buffersin declaration order: name, format, resolution, size for a fixed-size buffer or scale otherwise, clear (four floats, or null for Clear = None), mips, history, export, exportTarget (the render target's object path, or null), description
passesin declaration order — execution order within an injection point: name, kind, injection, enabled, reads / writes (slot, buffer, previous), params (target, source, and the constant, or the parameter with multiplier and offset), description, and settings — only the keys of the pass's own kind, slot numbers included
slotsevery HLSL pass again: pass, kind (compute or pixel), slot — the number a rebuild of another pipeline can move

The write guard covers the slot registry too: a pipeline with no file behind it is built in memory with the slots the registry already records for it (-1 where it records none), and neither the registry nor a snapshot is written.

Each node is { id, class, props, inputs }, plus reroute on a named-reroute usage. id is n0, n1, … — positional, never an engine identifier. class is the expression class's full path name. inputs lists only connected pins, in pin order, as { index, name, from, output, mask }, where mask is an "rgba"-style string or null. A named-reroute usage links to its declaration by name, because the name is the declaration's identity in the source while its GUID is reissued on every rebuild.

An input's name is the engine's own, with one exception (since 2.0.0): BreakMaterialAttributes, GetMaterialAttributes and SetMaterialAttributes name their inputs with translated text, so theirs are written as MaterialAttributes and the attribute's own name (BaseColor, FrontMaterial) -- a capture must not depend on the language of the editor that took it. For the same reason a Get and a Set carry their attribute lists in props as names, AttributeGetTypes and AttributeSetTypes, although every other FGuid is left out: for these two nodes the list is the behaviour.

The exclusion list

Determinism beats completeness: a fingerprint that changes between two runs of the same compiler cannot say anything about two different ones. Anything that is not a property of the graph is therefore left out rather than normalized.

ExcludedWhy
The object name, and the _12 numeric suffix the engine appends to itAssigned when a node is recreated; node identity is positional instead
MaterialExpressionGuid, VariableGuid, DeclarationGuid — every FGuidReissued on every regeneration
MaterialExpressionEditorX / EditorYNode coordinates; regeneration reassigns them from the Layout section anyway
NodeColor and every other colourA named reroute seeds its colour from its own path name, so a recreated node repaints itself
Desc, bCollapsed, bCommentBubbleVisible, bHidePreviewWindow, bShowOutputNameOnPin, bRealtimePreviewPresentation, not behaviour
GraphNode, Material, Function, SubgraphExpressionTransient, or back-pointers into the owner
FExpressionInput propertiesTheir exported text is the connected object's path; connections are recorded structurally by node id instead

props is otherwise every reflected UPROPERTY the plugin already counts as content — the same rule the divergence digest uses, so the two cannot disagree about what a hand edit is. Parameter name, group and sort priority, sampler type, texture object path, static switch default, Custom Code / OutputType / AdditionalOutputs / IncludeFilePaths, the function-call target path, ComponentMask channel flags, constants and Panner/Time settings are all in. Values are written whether or not they equal the class default — omitting a value because it happened to match a CDO would change the dump's shape when an engine default moved.

Floats are formatted %.9g; a non-finite value degrades to a string, since JSON cannot spell one.

Node order

nodes is a post-order depth-first walk from the sinks, so a node is numbered only after everything feeding it is, and from always names an earlier entry.

StepRule
1Sinks. For a material: every connected material property input, in EMaterialProperty enumerator order (for a ThinCustom instance, the hidden base's). For a function: its FunctionOutput expressions in SortPriority order, declaration order breaking ties
2From each sink, depth-first over that node's inputs in pin order; a named-reroute usage follows its declaration as if it were an input
3Everything unreached — dead nodes, CustomOutput sinks, unused function inputs — sorted by class path, then by the node's own props, then by array position

The engine's own GetExpressions() order is creation order: a property of the compiler that built the graph rather than of the graph, and exactly what two compilers cannot be expected to agree on. Step 3's final tiebreak is the one rule two compilers need not match; it exists so that a single capture is reproducible.

Diffing two captures

git diff --no-index -- I:/Baseline/before I:/Baseline/after

--no-index diffs two directory trees that are not in a repository, and the layout is designed for it: same tree shape, sorted keys, one value per line, LF endings. A file present on one side only is a source whose asset set changed. Every other hunk is a behavioural difference, and needs either a fix or an entry in the parity log.

Output is UTF-8 without a BOM, two-space indents, keys sorted case-sensitively at every level, LF line endings and a trailing newline.

dump-layout

(since 2.0.0) Draws where a layout computed on the IR would put the nodes of every product of a source, as one SVG per product and style, without building an asset. It is how to look at Blocks, Source Bands and Layered before choosing a Graph Layout Style. For Blocks the picture shows the boxes and the named reroutes between them: a declaration beside the value, a usage in every box that reads it, and no wire from one box to another.

OptionDefaultMeaning
-Source=<path> / -Allone is requiredwhich sources
-Style=AllBlocks, SourceBands, Layered, or All for one picture of each. Classic is the 1.x layout, which works on the finished graph and cannot be drawn from the IR (DSH9041).
-Out=<dir><Project>/Saved/DreamShader/Layoutroot of the output tree, laid out <root name>/<path relative to the root> like dump-ir's
-Jsonoffalso write the coordinates — positions, sizes, columns, bands, comment boxes, the count of long edges, and for Blocks the boxes, the reroutes between them and the reroutes in front of the outputs — beside each SVG

Files are named <source file>.<Product>.<Style>.layout.svg (and .json). Node sizes are estimated from the IR — a node's pins and its title — so the picture shows the arrangement, not the exact footprint a live node has. A file that cannot be written is DSH9040.

fmt

(since 2.0.0) The formatter is the language's own printer over a parse that kept its comments and blank lines; there is no second opinion about layout anywhere. fmt rewrites each file in place.

OptionMeaning
-Source=<path>one file — any 2.0 source, in any source root
-Allevery .dss, .dsi and .dsh under the writable source roots — the project's own; a plugin ships its sources as they are, and naming one of its files is how to format it anyway
-Checkwrite nothing; every file that would change is DSH9046 and the run fails — the form for CI
-Out=<dir>write the formatted copies under this directory, mirroring the source tree, instead of over the sources

What it guarantees, per file, before it writes a byte: the formatted text parses, to the same declarations, with every comment the file had, and formatting it again changes nothing. A file that fails any of those is left alone and reported as DSH9044 — a fault of the formatter, never of the file. The file's own line terminator is kept, so a format is never a whole-file diff.

Two kinds of file are left as they are, and neither is an error:

FileWhyCode
one with 1.x declarations — a .dsm, a .dsf, a .dsh that still has Function blockswhat the printer writes for those is 2.0 text, and turning 1.x into 2.0 is migrate, which proves far more before it writesDSH9042 (info)
one that uses #if outside a /// @custom bodythe parser reads preprocessed text and fmt reads the file as it is on disk; reprinting one side of an #if would delete the otherDSH9043 (info)

The run ends with one line: DreamShader fmt: 3 rewritten, 41 already formatted, 2 left alone, 0 failed of 46 file(s). RESULT=OK.

list-generated

(since 2.0.0) Names every asset the sources build — front end, binder and destination rules, and nothing after them: no asset is built, loaded or saved. It is what ignore rules and P4 typemaps are written from; Source control has the recipes.

OptionDefaultMeaning
-Source=<path> / -Allone is requiredwhich sources; -All covers plugin source roots too
-As=PackagesPackages — long package names; Files — paths relative to the project directory; GitIgnore — the same paths anchored with /, under a header comment; Json — every field (schema dreamshader-generated-assets, version 1)
-Out=<file>the logwrite the list here. A script should always pass it: the log is for reading.
-IncludeEphemeraloffalso list a ThinCustom material that is memory-only right now; it has no file, so it is left out of a list ignore rules are written from

A .dsp contributes two kinds of row (2.1.0): its pipeline (kind PassPipeline) and the render target of every exported buffer of a float or normalized format (kind PassExportTarget, <Pipeline>_<Buffer> in the pipeline's folder). The slot registry and snapshots under <DShader>/.dreampass/ are sources, not generated assets, and are not listed — see Source control.

A source that does not compile still contributes the products that were established before the failure, and the run ends RESULT=FAILED — a list is never silently short. Assets outside the project directory (an engine-level plugin's content) have no project-relative path; Files and GitIgnore say how many were left out (DSH9049). An unknown -As is DSH9048; a list that cannot be written is DSH9047.

With -All (after 2.1.2) the package assets are listed too — what a package's Assets sources and asset headers build (Packages) — and the package asset roots (/Game/DreamShaderPackages, /<Plugin>/DreamShaderPackages) are searched for assets no source builds any more: a removed package's, or a function a package stopped generating. Each is the warning DSH9111, and -As=Json lists them with "orphaned": true (every other row says false). The plain lists leave them out — they name what the sources build — and nothing is deleted.

.dsp sources

(since 2.1.0) A Custom Pass pipeline is a compilable source like the others, with one product — a UDreamPassPipeline — and no graph. What each verb does with one:

VerbOn a .dsp
compileBuilds the pipeline and the render target of every exported buffer, and gives each HLSL pass its slot: planned, pre-checked, its snapshot and the registry written under <DShader>/.dreampass/ (see pass-registry). A material one of its passes names that is missing or older than its source is compiled first. A commandlet recompiles no shader; the next editor start compiles the changed registry. Below UE 5.8 the compile is refused, DSH8300
checkReads every material and .usf the passes name and makes every check the binder makes. Compiles nothing first, builds nothing, and compiles no HLSL
check -ShadersBuilds and saves the pipeline as compile does, then pre-checks every HLSL pass in its slot — see Shader check of a pipeline
dump-irThe product as text: the pipeline's keys, one line per parameter and buffer, and per pass its keys, its kind's settings and its bindings, as the binder settled them — (inferred) marks a buffer resolution the source did not write, (from the shader) a group size read off [numthreads]. -Json adds the JSON form
dump-layoutWrites nothing for it: a pipeline has no graph to lay out. Not an error
indexThe symbol index, with a pipeline section: the #pragma pipeline values as bound, the uniforms, the buffers and the passes with their bindings and spans, and the injection-point and format spellings an editor completes from
fmtThe printer's layout, with the same guarantees as for a .dss; -All includes the .dsp files of the writable source roots
list-generatedThe pipeline and the render targets of its exported buffers — see list-generated
dump-graphOne JSON for the pipeline — see A pipeline's dump
decompileThe way back: -Asset naming a pipeline, or -SourceFile naming a .dsp, writes a .dsp — see Decompiler

Shader check of a pipeline

check -Shaders on a .dsp compiles no material: a pipeline has none, and the materials its passes name are checked by the sources that build them. What can fail to compile is an HLSL pass, and that is pre-checked in its global shader slot instead — the pre-check a compile runs, with three differences:

  • Every HLSL pass, changed or not. A compile pre-checks only the passes whose snapshot changed. The check plans the slots against the registry as it stands — a pass that has a slot keeps it, a new one is checked in the slot it would take — and commits nothing of that plan.
  • The -Platform formats, when the run names any: SM6 is PCD3D_SM6, SM5 PCD3D_SM5, ES3_1 PCD3D_ES3_1, VULKAN_SM6, VULKAN_SM5 and METAL_SM5 the SF_ formats, and any other token is taken as a shader format name. A token that names no shader format this machine can compile — a target-platform name such as Windows among them — is not pre-checked, with DSH8324 saying so. Without -Platform, the formats a compile pre-checks for: those of the active feature levels and every format the active target platforms target, with DSH8324 for one this machine has no compiler for. -Quality and -Timeout do not apply to slots.
  • Its errors count as shader errors in the summary line. A compile error is DSH8322, at the line of the .usf that caused it; a .dsp with no HLSL pass reports DSH8339 (info) and passes.

pass-registry

(since 2.1.0) The Custom Pass HLSL slot registry from the command line. Every HLSL pass of every .dsp — a compute pass, or a fullscreen pass with Shader = — runs in one slot of the global shaders FDreamPassCS and FDreamPassPS, and the files that map slots to passes are committed with the sources (HLSL passes):

<DShader>/.dreampass/
├─ Registry.json          the record the other files are written from
├─ RegistryCompute.ush    one section per compute slot; what FDreamPassCS includes
├─ RegistryPixel.ush      the same for FDreamPassPS
└─ Slots/C00/, P03/, …    one snapshot per slot that has one

There is one registry per project, under the project's Source Directory; a plugin's .dsp takes its slots in it like any other. Registry.json (schema dreamshader-pass-registry, version 1) holds two arrays, compute and pixel, with one entry per slot: slot, pipeline (the object path), pass, kind (compute or fullscreen), source (the .dsp, project-relative), shader (project-relative), entry, hash, formats, files (the snapshot's, relative to the slot directory) and section (the text of the slot in its registry file).

Compiles keep the registry current on their own. The verb is for what a compile cannot see: a .dsp that was deleted or renamed, a merge that broke Registry.json, a checkout that lost snapshot files.

OptionEffect
(none)Lists every slot and what it is. Writes nothing
-GcFrees every PipelineGone and PassGone slot
-RebuildMoves an unreadable Registry.json aside, compiles every .dsp again, frees the garbage and rewrites the registry files. -Gc beside it adds nothing

The options follow the argument rules: -Gc, --gc and -Gc=true are one switch, and -Rebuild wins when both are given. The dsc.ps1 switches are -Gc and -Rebuild; from a PowerShell prompt, ./dsc.ps1 pass-registry --gc is not the switch — PowerShell binds --gc to the script's positional target, which this verb ignores, and the run only lists.

The listing

LogDreamShader: Display: DreamShader pass-registry: C:/Projects/MyGame/DShader/.dreampass/Registry.json
LogDreamShader: Display:   C00  Live             /Game/Passes/CP_Highlight.CP_Highlight  pass 'Blur'  DShader/Passes/BoxBlur.usf : BlurCS  [PCD3D_SM5, PCD3D_SM6]
LogDreamShader: Display:   C01  PipelineGone     /Game/Passes/CP_Old.CP_Old  pass 'Simulate'  DShader/Passes/Old.usf : MainCS  [PCD3D_SM6]
LogDreamShader: Display: DreamShader pass-registry: 2 of 32 compute slot(s), 0 of 16 pixel slot(s) taken; 1 to collect with -Gc. RESULT=OK

One line per slot, the compute table first, each in slot order:

Column
C07 / P03the table — compute or pixel — and the slot number: the slot directory's name
statebelow
pipelinethe UDreamPassPipeline's object path
pass '<Name>'the pass
<shader> : <entry>the shader file, project-relative, and the entry; - for one the record does not have
[<formats>]the shader formats the snapshot passed its pre-check for. A format the project targets since is one the snapshot was never checked for
StateMeaning-Gc
Liveits pipeline's .dsp runs that pass in HLSL in that table, and the snapshot is on diskkeeps it
Reservedrecorded without a snapshot that passed a pre-check: the slot compiles to the empty stub, and its pass does nothing until its .dsp compiles againkeeps it
SnapshotMissingthe snapshot's files are not on disk — a Slots folder that was not committed. The next compile of its .dsp writes them; until then a process that loads the DreamShaderPass module takes the slot's section out of the registry file at startup, logged as an error, so the global shaders still compile. DSH9208keeps it
PipelineGoneno .dsp under the source roots builds its pipeline any more. When the pipeline asset still exists, DSH9201 warns that its pass still points at the slot: once the slot is freed and given to another pass, that pass's shader is what the old asset would runfrees it
PassGoneits pipeline's .dsp compiles, and no longer runs that pass in HLSL in that tablefrees it
Unknownits pipeline's .dsp does not compile far enough to tell. DSH9209keeps it

To tell Live from PipelineGone, PassGone and Unknown, the listing runs the front end of every .dsp that owns a slot; nothing is built or written. A slot is garbage only on positive evidence — no source builds its pipeline, or its pipeline's source compiles and has no such pass — so a source that does not compile keeps every slot it has.

-Gc

Frees every PipelineGone and PassGone slot — its entry in Registry.json, its section in the registry file, its snapshot — and deletes every slot directory nothing names. Each freed slot is an info, DSH9202, after the listing's warnings. Nothing is recompiled in the commandlet; the next editor start compiles the changed registry.

LogDreamShader: Display: DreamShader pass-registry -Gc: 1 slot(s) freed. RESULT=OK

Run it after deleting or renaming a .dsp: its slots stay taken until they are collected, and a compile that finds every slot of a table taken (DSH8316) names this verb.

-Rebuild

Step
1A Registry.json that does not parse — a merge conflict left in it is the usual reason — is moved aside to Registry.json.unreadable (DSH9203), and an empty registry is written so the compiles below assign every slot afresh. One that parses is left as it is
2Every .dsp under the source roots, Packages folders excluded, is compiled in path order, forced, as compile -Force would: each plans its slots against the registry and writes the snapshots it is missing. One that fails is DSH9204 and keeps the slots it had; with no .dsp at all the step is DSH9206 (info)
3The garbage is collected, as -Gc does, without its missing-snapshot warnings
4RegistryCompute.ush and RegistryPixel.ush are written from Registry.json as it stands. A slot whose snapshot files are still missing becomes reserved (DSH8337), because a registry that includes a missing file fails the global shader compile. Every slot directory nothing names is deleted
5For every .dsp that failed, each HLSL pass of its existing pipeline asset is checked against the registry: a slot the registry now gives to another pass, or leaves free, is DSH9205 — until its source compiles, that pass runs whatever the slot holds
LogDreamShader: Display: DreamShader pass-registry -Rebuild: 4 .dsp compiled, 0 failed, 1 slot(s) freed, 0 slot(s) reserved for a missing snapshot. RESULT=OK

It builds and saves the pipelines, as compile does. Run it after a merge conflict in Registry.json — a compile refuses to write over a registry that does not parse (DSH8315), and so does -Gc (DSH8335) — after a checkout that lost snapshot files, or whenever the listing and the sources disagree. Commit the whole .dreampass/ folder with the result.

A registry file that cannot be written is DSH8326, and one that cannot be moved aside DSH8336; a file that is read-only because it is not checked out is the usual reason for both.

Argument syntax

Shared by every command. Pinned by the automation tests DreamShader.Commandlet.Args.SplitAndGet and DreamShader.Commandlet.Args.FlagValues.

RuleBehaviour
Key normalizationtrim, then strip all leading -, then trim again. -Source, --Source and Source are the same key
Name matchingcase-insensitive — -source, -SOURCE, -Source are equivalent
Value normalizationtrim, then strip one layer of surrounding quotes, then trim again
Assignment spliton the first =; a value may therefore contain =
Dashless assignmentbare Key=Value (no leading dash) is accepted wherever -Key=Value is
Search orderthe parsed parameter map, then the switch list, then the bare-token list; the first match wins
Empty value-Source= resolves to an empty string and is treated as absent
Missing keyabsent

Boolean flags

A flag may be written bare or with a value. The value is matched case-insensitively, after the value normalization above.

Written asResult
-Forceon
-Force=1, -Force=true, -Force=yes, -Force=onon
-Force=0, -Force=false, -Force=no, -Force=offoff
(not written)off
-Force=<anything else>, an empty -Force= includederror DSH9110

An error is not a guess at what was meant. The command logs DSH9110, does nothing at all and exits 1: migrate -Check=banana neither checks nor writes, and -All=never selects nothing. Every command reads all of its flags before it touches a file.

The engine's parser moves every -Name=value out of the switch list into the parameter map before the command sees it, so a flag is looked up in the same order as a value: the map, then the switches, then the bare tokens (a dashless Force=true is a token). A flag a command does not take is ignored, like any other unknown switch: dump-graph -Force=banana runs, because dump-graph never reads -Force.

[!NOTE] In 2.1.0 and earlier a flag written with any value was read as absent: the lookup searched the switch list only, where the engine never leaves -Name=value. -Force=true did not force, and migrate -Source=X -Check=true wrote the migration. This page used to say the opposite — that an unrecognized value meant on.

Exit codes

CodeCondition
0the selected command reported success
0compile -All resolved an empty source list — logged Warning, treated as success
1no command token and no Command= value
1unknown command name
1compile with none of -Source / -File / -All
1any per-file guard failure or compile failure during compile
1decompile without -Asset, or a load / decompile / write failure
0dump-graph -All resolved an empty source list — logged Warning, treated as success
1dump-graph with none of -Source / -File / -All
1any per-file guard failure, generation failure or dump-write failure during dump-graph
0pass-registry listed the registry — whatever the listing found; its warnings do not fail the run (2.1.0)
1pass-registry could not read Registry.json (DSH9200)
1pass-registry -Gc could not read Registry.json (DSH8335) or write a registry file (DSH8326)
1pass-registry -Rebuild: the unreadable Registry.json could not be moved aside (DSH8336), a .dsp did not compile (DSH9204), or collecting or rewriting failed
1any command: a flag it reads has a value that is neither on nor off (DSH9110); nothing was run — see Boolean flags

Notes

  • The editor bridge never runs inside a commandlet. The editor module returns from startup as soon as it detects a commandlet process, so there is no source-directory watcher, no auto-compile-on-save, no WebSocket server on port 17864, no diagnostics.json writer, no bridge.db, and no menu registration. The only exception is the cook commandlet, which installs a post-engine-init hook. See Editor bridge.
  • The commandlet writes real packages. compile asks for every ThinCustom product Materialized, so /Game/... .uasset files are created and saved on disk. The interactive editor builds a ThinCustom product Ephemeral (memory-only) unless it is already saved; a Graph material and a material function are saved to disk by both. This is the intended way to materialize a whole project's sources in CI. See Ephemeral materials.
  • Because assets are persisted, a commandlet run can leave assets on disk that shadow the editor's Ephemeral materials. Tools ▸ DreamShader ▸ Make Ephemeral removes them.
  • Cooking is a separate commandlet. On the cook director only (a process whose -run= contains Cook and that does not carry -cookworker), DreamShader materializes every project source as a persistent asset before the cook proper. A generation failure there is Fatal and aborts the cook: DreamShader cook generation failed for {Count} source file(s); aborting the cook. See the [Cook] Failed entries above. A source that belongs to a plugin the project does not write into — an engine or marketplace plugin — is not counted there: it is compiled like any other, and a failure is logged as [Cook] Not this project's to build (plugin '<Name>') without aborting, because those assets ship with that plugin. See Cook behaviour.
  • -Force bypasses the source-hash skip only; it does not delete anything. See Caching.
  • A .dsp compile writes outside Content/ (2.1.0): the HLSL slot registry and its snapshots under <DShader>/.dreampass/, which are sources to commit — see Source control. A commandlet writes them and recompiles no shader; an editor recompiles the slot shaders as soon as they change.
  • Adding -NoDreamShaderEditorBridge to a non-commandlet automation run (for example -ExecCmds="Automation RunTests …") suppresses the bridge there too.

Diagnostics

Runtime substitutions are shown as {Placeholder}. All messages go to LogDreamShader.

MessageSeverityCause
(the usage banner)Errorno command token and no Command= value
Unknown DreamShader command '{Command}'. + the usage bannerErrorthe command is none of the spellings under Commands
(the usage banner)Errorcompile with neither -Source / -File nor -All
DreamShader commandlet found no source files to compile.Warningthe resolved source list is empty; the run still exits 0
DreamShader compile requires a .dss, .dsi, .dsp, .dsm or .dsf file: {Path}Errorthe file is not a DreamShader source, or is a .dsh header
(the compile result message)Display / Errorper-file outcome; see Result messages
(the usage banner)Errordecompile with neither -Asset nor -SourceFile
DreamShader decompile: -Format takes Dss, Legacy or Auto; got '{Value}'.Erroran unknown -Format
DreamShader could not load asset '{AssetPath}'.Errorthe asset failed to load under both the normalized and the raw path
DreamShader decompile: {Error}Errorthe -SourceFile source does not resolve, or nothing it builds exists yet — compile it first
DreamShader failed to decompile '{LoadPath}': {Error}Errorthe decompiler reported failure
DreamShader decompile supports Material and MaterialFunction assets only: {AssetPath}Errorunsupported asset class for -Format=Legacy (surfaced through the message above); the 2.0 decompiler answers DSH9086
Decompile did not produce source text.Errordecompiler failed with no error text
DreamShader failed to resolve an output file path.Errorthe destination path resolved empty
DreamShader failed to create output directory '{Directory}'.Errorthe destination directory could not be created
DreamShader failed to write decompiled source '{Path}'.Errorthe file could not be written
DreamShader decompiled '{LoadPath}' to '{OutputPath}' ({Format}).Displaysuccess; {Format} is dss or legacy
DreamShader commandlet found no source files to dump.Warningdump-graph resolved an empty source list; the run still exits 0
DreamShader dump-graph requires a .dss, .dsi, .dsp, .dsm or .dsf file: {Path}Errorthe file is not a DreamShader source, or is a .dsh header
DreamShader failed to dump '{Path}': {Error}Errorgeneration or the dump failed; see DSH9030–DSH9034
Dumped {Kind} {AssetPath} ({N} nodes) to {File}.Displayone asset dumped; (read from disk; not regenerated) is appended when the asset already had a file
DreamShader dump-graph wrote {N} graph dump(s) from {M} source file(s) to {Directory}.Displayend-of-run summary
{N} of them already exist on disk, so dump-graph read them as they stand instead of rebuilding them (it never writes an asset). Run 'compile -All -Force' first if those sources have changed since.Warningsee It never writes an asset
DreamShader pass-registry: {Registry.json path}Displaythe head of a listing (2.1.0)
DreamShader pass-registry: {N} of {M} compute slot(s), {N} of {M} pixel slot(s) taken; {N} to collect with -Gc. RESULT=OKDisplaythe end of a listing
DreamShader pass-registry: the registry could not be read. RESULT=FAILEDErrorafter DSH9200
DreamShader pass-registry -Gc: {N} slot(s) freed. RESULT={OK|FAILED}Display / Errorthe end of -Gc
DreamShader pass-registry -Rebuild: {N} .dsp compiled, {N} failed, {N} slot(s) freed, {N} slot(s) reserved for a missing snapshot. RESULT={OK|FAILED}Display / Errorthe end of -Rebuild
DreamShader pass-registry -Rebuild: the unreadable registry could not be reset. RESULT=FAILEDErrorafter DSH8336
DSH9110: '-{Flag}={Value}' is neither on nor off. A flag takes true, 1, yes or on, or false, 0, no or off, and '-{Flag}' alone is on; the command did nothing.Errorany command: a flag it reads has another value, an empty one included; see Boolean flags
DreamShader {verb}: a flag's value is neither on nor off, so nothing was run. RESULT=FAILEDErrorafter DSH9110, from check, dump-ir, dump-layout, index, fmt, list-generated and pass-registry

The usage banner, verbatim:

Usage:
  -run=DreamShader compile -Source="C:/Project/DShader/File.dsm" [-Force] [-Define=NAME=VALUE ...]
  -run=DreamShader compile -All [-Force] [-Define=NAME=VALUE ...]
  -run=DreamShader decompile { -Asset="/Game/Path/Asset.Asset" | -SourceFile="C:/Project/DShader/File.dss" } [-Out=<file>] [-Format=Dss|Legacy|Auto] [-KeepAssetPath] [-Readable] [-DiagnosticsOut=<file>]
  -run=DreamShader migrate { -Source="C:/Project/DShader/File.dsm" | -All | -Root=<source root> } [-Check] [-DryRun] [-Out=<dir>] [-NoBackup]
  -run=DreamShader dump-graph { -Source="C:/Project/DShader/File.dsm" | -All } [-Out="C:/Project/Saved/DreamShader/GraphBaseline"]
  -run=DreamShader check { -Source="C:/Project/DShader/File.dss" | -All } [-Shaders] [-Platform=SM6,SM5] [-Quality=High] [-Timeout=120] [-DiagnosticsOut=<file>]
  -run=DreamShader dump-ir { -Source="C:/Project/DShader/File.dss" | -All } [-Out=<dir>] [-Json]
  -run=DreamShader dump-layout { -Source="C:/Project/DShader/File.dss" | -All } [-Style=Blocks|SourceBands|Layered|All] [-Out=<dir>] [-Json]
  -run=DreamShader index { -Source="C:/Project/DShader/File.dss" | -All } [-Out=<dir>]
  -run=DreamShader export-catalog [-Out=<file>]
  -run=DreamShader fmt { -Source="C:/Project/DShader/File.dss" | -All } [-Check] [-Out=<dir>]
  -run=DreamShader list-generated { -Source="C:/Project/DShader/File.dss" | -All } [-As=Packages|Files|GitIgnore|Json] [-Out=<file>] [-IncludeEphemeral]
  -run=DreamShader pass-registry [-Gc | -Rebuild]
decompile writes 2.0 text by default -- a .dss for a Material or MaterialFunction, a .dsi for a
MaterialInstanceConstant, a .dsp for a DreamPassPipeline; -Format=Legacy, or an -Out ending in .dsm
or .dsf, writes the 1.x text (a DreamPassPipeline has none).
-SourceFile decompiles every asset that source builds into one file; -KeepAssetPath keeps each asset's own path.
migrate rewrites 1.x sources (.dsm, .dsf, .dsh) as .dss; -Check verifies the rewrite and writes nothing.
-All takes the writable source roots; -Root names one root, a plugin's included, by its name or its plugin's.
fmt rewrites 2.0 sources in the printer's layout (-All: the writable roots); -Check writes nothing and fails
when a file would change. list-generated names every asset the sources build, building nothing.
compile, dump-graph, check, dump-ir and index take any compilable source -- .dss, .dsi,
.dsp, .dsm or .dsf; a .dsh header is compiled through the sources that include it.
pass-registry lists the Custom Pass HLSL slots and what each is (Live, Reserved, SnapshotMissing,
PipelineGone, PassGone, Unknown), writing nothing; -Gc frees the PipelineGone and PassGone ones;
-Rebuild compiles every .dsp again, frees the garbage and rewrites the registry files from
Registry.json -- a Registry.json that does not parse is moved aside and every slot assigned afresh.
check writes no asset at all; -Shaders is the exception -- a shader
compile needs a real material, so it builds and saves the products the way compile
does, then reports HLSL errors as stage: shader.
dump-graph is a developer tool: it writes one canonical JSON per generated asset and
never writes an asset itself. Compile the tree first if its sources have changed.
-Define (short form -D) may be repeated; -Define=NAME with no value is a bare marker that
defined(NAME) sees. Names starting with DS_ are reserved for the built-in constants.
A flag is written bare (-Force) or with true/false, 1/0, yes/no or on/off (-Force=false);
any other value is an error (DSH9110), and the command does nothing.

Example

Compile a single source, bypassing the hash skip:

& "C:\Program Files\Epic Games\UE_5.5\Engine\Binaries\Win64\UnrealEditor-Cmd.exe" `
  "C:\Projects\MyGame\MyGame.uproject" `
  -run=DreamShader compile -Source="C:/Projects/MyGame/DShader/Materials/M_Sample.dsm" -Force `
  -unattended -nopause -nosplash -stdout -log

Compile every project source — .dsf first, then .dsm — as a CI gate:

& "C:\Program Files\Epic Games\UE_5.5\Engine\Binaries\Win64\UnrealEditor-Cmd.exe" `
  "C:\Projects\MyGame\MyGame.uproject" `
  -run=DreamShader compile -All -Force `
  -unattended -nopause -nosplash -stdout -log

Compile the whole tree twice, once per branch of a #if, as the CI gate that keeps an inactive branch from rotting — an untaken branch is never parsed, so nothing else checks it:

foreach ($Legacy in 0, 1) {
    & $UnrealEditorCmd $Project `
      -run=DreamShader compile -All -Force `
      -Define=MOONTOON_LEGACY_TOON=$Legacy `
      -unattended -nopause -nosplash -stdout -log
    if ($LASTEXITCODE -ne 0) { throw "DreamShader failed with MOONTOON_LEGACY_TOON=$Legacy" }
}

Decompile an existing material to a chosen path:

& "C:\Program Files\Epic Games\UE_5.5\Engine\Binaries\Win64\UnrealEditor-Cmd.exe" `
  "C:\Projects\MyGame\MyGame.uproject" `
  -run=DreamShader decompile -Asset="/Game/Materials/M_Existing" `
  -Out="C:/Projects/MyGame/DShader/Decompiled/Materials/M_Existing.dsm" `
  -unattended -nopause -nosplash -stdout -log

A relative -Source is resolved against DShader/ first:

-run=DreamShader compile -Source="Materials/M_Sample.dsm"

Capture a graph baseline for the whole project, then compare it against a later one — the two-step shape matters, because dump-graph will not rebuild an asset that already exists on disk:

& $UnrealEditorCmd $Project -run=DreamShader compile -All -Force `
  -unattended -nopause -nosplash -stdout -log
& $UnrealEditorCmd $Project -run=DreamShader dump-graph -All -Out="I:/Baseline/before" `
  -unattended -nopause -nosplash -stdout -log

# …change the compiler, rebuild, capture again…

git diff --no-index -- I:/Baseline/before I:/Baseline/after

After a merge that conflicted in DShader/.dreampass/Registry.json, rebuild the slot registry from the sources, then list it:

& $UnrealEditorCmd $Project -run=DreamShader pass-registry -Rebuild `
  -unattended -nopause -nosplash -stdout -log
& $UnrealEditorCmd $Project -run=DreamShader pass-registry `
  -unattended -nopause -nosplash -stdout -log

Console output of a successful two-file -All run:

LogDreamShader: Display: Generated MaterialFunction /Game/Functions/MF_Noise.MF_Noise from C:/Projects/MyGame/DShader/Functions/MF_Noise.dsf.
LogDreamShader: Display: Generated Material /Game/Materials/M_Sample.M_Sample from C:/Projects/MyGame/DShader/Materials/M_Sample.dsm.

See also

On this page