DreamShader Plugin
Release notes for the DreamShader Unreal plugin, newest first, taken from the plugin's own CHANGELOG.
Release notes for the Unreal plugin, newest first. Entries record what changes for you — the language surface, the generator, the editor tooling and engine compatibility.
| Current release | 1.8.0 — 2026-08-21 |
| Engines | Unreal Engine 5.3 through 5.8, Win64 |
| Source of record | the plugin's own CHANGELOG.md, shipped in the plugin folder |
1.8.0 - 2026-08-21
The release about not losing things. Generation used to tear an asset down unconditionally: a hand-edited asset was overwritten silently, a failed rebuild left the asset empty, and an asset left open in the material editor had the rebuild quietly reverted by the next Apply. This version closes all of those — an output fingerprint, an atomic rebuild, an open-editor gate, dependency-ordered batches, and a build key that finally covers everything deciding what a source compiles into.
Divergence — three ways out
- Every successful generation stamps a
DreamShader.OutputDigestfingerprint of what the asset actually holds, and the next rebuild compares it before anything is cleared. A mismatch fails the compile with the asset exactly as you left it — not cleared, not half-built. See Regeneration. - The digest covers exactly what a rebuild would destroy: nodes, connections, node properties, the reset-property set, a function's asset-level fields, and a ThinCustom instance's parameter overrides. It deliberately excludes what a rebuild never touches — node positions, comment boxes you wrote, pin GUIDs, and properties outside the reset list. Blocking on a property that was never in danger only refuses a rebuild for nothing.
- All three ways out are on the asset's right-click menu under DreamShader: Revert to Source
(discard the edits and rebuild), Adopt Into Source (rewrite the
.dsm/.dsffrom the asset, backing the old one up to<source>.bak), and Detach From DreamShader (keep the asset, stop managing it). -Forcedoes not get past it.bForceanswers "is the source hash stale", and the editor asserts it for every file in its own startup sweep — honouring it would have left the gate dead in the mode the editor spends all its time in. Only Revert overrides a divergence, because only a person can make that call.- Memory-only assets are stamped too, with the digest and the source path (but not the source hash, which would switch the skip on). Without the path an in-memory asset classifies as foreign and the gate never fires — dead in the editor's default mode.
A rebuild is atomic
- The steps either all take effect or none of them do. The old graph is now detached rather than
destroyed, and destroyed only once the rebuild has fully succeeded; a failure restores the asset
from a serialized snapshot — render state, material-function usage, node graph, connections and
FunctionInput/FunctionOutputpin GUIDs all come back, so existing call sites stay wired. - This was necessary because not every failure can be caught up front: the whole-file parse, the
Settingsvalidation and theOutputsvalidation all run before the asset is touched, but aGraphblock is compiled one statement at a time by the graph builder, which runs after the teardown. Before1.8.0such a failure left the asset emptied — bad for a material, much worse for a material function, whose call sites read their pins from the live asset: one bad.dsftook every material that called it down with it, with no undo (generated assets are deliberately notRF_Transactional). - Two things fall out of it. The teardown's two deletion strategies collapsed into one — the
node-by-node path existed to break inbound links, which is pointless when the graph leaves as a
unit — so the former 1200-expression threshold is gone and every rebuild takes the fast path. And
DependentFunctionExpressionCandidates, the second serialized node list whose stale entries used to arm a null-deref for the rest of the session after a failed function compile, is now reset at commit rather than at teardown.
A rebuild is refused while the asset is open in an asset editor
- An asset editor does not edit the asset:
FMaterialEditorduplicates it into a transientUPreviewMaterialand copies that duplicate back over the original on Apply or Save; the material instance editor writes back through aUMaterialEditorInstanceConstantwrapper. An editor left open across a rebuild is therefore holding a pre-rebuild copy, and the next Apply silently reverts everything the rebuild did — surfacing later as a divergence report on the compile after that, a long way from the cause. - Compiles now stop with
Asset '{ObjectPath}' is open in an asset editor, so it was NOT rebuilt. …and say to close it. - Revert to Source and Adopt Into Source are the exception: they close the editor themselves, act, and reopen it. Both are offered on that editor's own toolbar, and a menu item that is permanently dead where it is most useful is not a guard, it is a bug.
The build key replaces the plain source hash
DreamShader.SourceHashis now a build key: prepared source text, the Default Compiler Backend, the three mapping tables, the plugin version plus a hand-bumped format tag, and the engine version. It used to hash the source text alone, so anything else that changes the output left every already generated asset looking current.- The forced full sweep that was bolted onto the backend setting is therefore gone: each affected asset fails the skip check on its own, and one the setting does not affect is still skipped instead of being needlessly rebuilt.
- Changing the key's composition invalidates every existing stamp — one rebuild per asset, once, which is the intended effect.
A batch of source files compiles in dependency order
- A
.dsmthat calls aShaderFunctionbinds its call node against the liveUMaterialFunctionasset —SetMaterialFunctionreads the pins off the object, not off the source — so compiling the caller first bound it against the previous version of that function's interface. Renaming a function input and saving both files was enough to hit it, and which one won depended on the iteration order of aTMap. - Both drain points — the watcher's pending-file batch and the whole-project sweep — now topologically sort by the import graph. Only edges inside the batch are honoured; a cycle is left for the import loader to reject with its own diagnostic.
One editor owns the bridge for a project
- The bridge directory is per project, not per process: one
Requestsfolder, onestatus.json, one heartbeat. Two editors open on the same project therefore both polled the same queue and both overwrotestatus.jsonwith their own pid. - Ownership is now a lock file,
Bridge/owner.lock, carrying the owning pid and a heartbeat: an owner is believed while its process is alive and its heartbeat is under 30s old. Both tests are needed — the pid test alone hands the bridge over whenever the owner is mid-compile (a compile blocks the game thread), and the heartbeat test alone leaves it unowned after a hard crash. It is released on shutdown. - A non-owning editor still compiles its own in-memory materials, but does not consume requests, write
status.json, or write generated assets to disk. That last part follows directly from storage deciding how a rebuild persists: without it, two editors would bothSavePackagethe same file, and a save that loses that race is not a merge — it is a corrupted package or a dead editor. The commandlet is unaffected. See Editor Tools.
An asset that exists on disk is rebuilt on disk
- Storage decides how a rebuild persists, not the compile that asked for it. The editor asks for a memory-only compile every time, and when that landed on a package which already existed, generation rebuilt it in place and then cleared the dirty flag — producing an object that matched neither the file on disk nor anything that would ever be written, and that reported itself clean. A Save All could persist a state nobody chose, and the version visible in the editor disappeared on restart. The only signal was one log warning.
IsGeneratedAssetPersistednow decides: an asset with a file behind it takes the persisted path — stamped, saved, dirty flag never faked — and one without stays memory-only. There is exactly one answer to "what is this asset" again. See In-memory Materials.- The startup sweep stopped forcing as part of this, and had to. Forcing was free while every in-memory asset regenerated regardless of its source hash; it stopped being free the moment a disk-backed asset started being saved, because every editor launch would then rewrite every persisted generated asset. Changing the Default Compiler Backend still forces, because the hash cannot see it.
Fixed
- A
Domain="Volume"material carriesbUsedWithVolumetricCloud, and decompiling one no longer turns the flag back on. The flag is now derived from the domain, and an explicitbUsedWithVolumetricCloud = "false";still wins — a Volume material that only feeds volumetric fog can decline the cloud shader permutations. It is written throughUMaterial::SetUsageByFlag(UE 5.8 deprecates everybUsedWith*field), and the assignment moved out of theDomainbranch to read the domain back off the material, becauseResetMaterialToDefaultsdoes not clear usage flags. The decompiler goes through the sameGetDefaultUsedWithVolumetricCloud, soUMaterial→.dsm→UMaterialis an identity again. See Material Settings. Reported in #27. - A swizzle survives the material editor. A component selection like
CustomStencil.rwas written as an inline mask on the connection (OutputIndex=0plusMask/MaskR), which the HLSL translator honours but the material graph editor cannot draw: pins correspond to an expression'sOutputsentries, and no pin means "output 0, red channel only".UMaterialGraph::GetValidOutputIndexdistrustsOutputIndex 0whenever a mask is present, finds no matching pin, and falls back to the node's last output — which the first Apply/Save then writes back. On aSceneTexturenode (Color / Size / InvSize) that turnedCustomStencil.rintoInvSize, silently, with no error anywhere: the stencil comparison failed for every pixel and the effect the function gated simply stopped appearing. Any generated asset touched through the material editor was exposed, and duplicating one first did not help. Component selections are now emitted in a form the graph can round-trip. An inline mask is kept only where the editor resolves it back to the pin it already names; where the expression publishes the same value through several masked outputs (TextureSample'sRGB/R/G/B/A/RGBA,VertexColor, …) the matching output is named directly; otherwise a realComponentMasknode is emitted. Mixed output sets likeSceneTexture's are never retargeted, because there the outputs are different values rather than views of one. The build key tag moves toDSK2, so every generated asset is rebuilt once into the new form. UE.CollectionParam(...) Name;inPropertiesis as wide as the collection parameter. The parser records the declaration form as a scalar (it cannot open the collection), and the generator used that width as-is, so a vector MPC parameter passed to afloat4input was widened by threeAppendVectorsplats and the material failed withCan't append float4 to float4. The width now comes from the loaded collection (vector → 4, scalar → 1); an explicitOutputType=still wins. See Properties.- A render target is accepted where a texture of its dimension is expected. The dimension check
compared asset classes; it now reads
UTexture::GetMaterialType()— the same answer the material compiler gives on a texture-object pin — soUTextureRenderTargetVolume,UTextureRenderTarget2D,UTextureRenderTargetCubeandUTextureRenderTarget2DArraypass for their dimension, and anything else a texture-object pin would take does too. - Cook-generated assets are registered with the AssetRegistry, so they reach the package. A cook
request is resolved through
IAssetRegistry::DoesPackageExistOnDisk, which consults only the registry's in-memory state and has no filesystem fallback; generation runs on post-engine-init, after the registry enumerated the content directories, so a freshly written package was invisible to that lookup — the material simply was not in the pak, andLoadObjectfailed at runtime in a Shipping build, with no error anywhere. Generation now records what its saves actually wrote and hands the filenames toIAssetRegistry::ScanModifiedAssetFilesbefore the commandlet'sMaincollects the initial requests. Reported in #26. - The ThinCustom instance path runs the ownership guard. It checked only the class of whatever sat
at the target path, not its provenance, so generating onto a hand-authored
UDreamShaderMaterialInstanceadopted it and then cleared its parameter overrides. Since ThinCustom is the default backend this was the widest of the three creation paths and the only one without the check. DreamShaderGraphDecompilerHelpers.hcompiles on its own — it declared functions takingEMaterialDomainandEBlendModewithout including either enum's header, which a unity build only ever had through a neighbouring translation unit.
Added
#includeat the top of aFunctionbody is hoisted to file scope. AFunctionblock's HLSL used to be emitted verbatim between the braces of the generatedDreamShaderFn_*definition, which made any#includeland inside a function — fine for macro-only headers, a compile error for anything that defines a function. Leading#include "…"/#include <…>directives (only whitespace and comments before them) are now stripped from the body and emitted once, in first-seen order, right after the guard of the generated.ush; aSelfContainedembed or aGraphFunctionbody puts them on the Custom node'sIncludeFilePathsinstead, ahead of the generated include. A directive after the first statement keeps the old behaviour. This is what lets a header shared between C++ and HLSL be consumed from a.dsfwithout copying its functions into DreamShaderLang. See Functions.
1.7.1 - 2026-08-16
Fixed
- Closing the editor crashed after using a Graph breakpoint. An access violation reading
0x3b0inUMaterial::GetExpressionInputDescription, from the probe preview's own destructor, reached throughFDreamShaderPreviewWebSocketServer::Shutdownwhile modules were unloading. Modules unload from inside the exit path:EngineExit()raises the exit request,FEngineLoop::Exit()runs the purge, and only then callsUnloadModulesAtShutdown()— so a module tearing down its own state at that point is doing it after the objects it points at are gone.TStrongObjectPtrkeeps the preview material out of the garbage collector's reachable-set sweep, but it does not exempt it from the exit purge: the pointer stayed non-null while the object behind it did not, which is why the existing null checks passed and the dereference still faulted. What it faulted on is worth naming, because nothing about the call site suggests it —UMaterial::GetExpressionInputDescriptionandGetExpressionCollectionboth dereferenceGetEditorOnlyData()without checking it, so reaching for a material's inputs or expressions after the purge is an unconditional null-plus-offset read rather than a recoverable failure. The teardown is now skipped once the engine is exiting, which is correct and not merely safe: releasing the shared expression collection exists so the preview material cannot touch the graph material's nodes later, and at exit there is no later. The ordinary path, where a client disconnects while the editor keeps running, is unchanged.
1.7.0 - 2026-08-16
Added
- Graph breakpoints in the live preview — "Start Previewing Node" for a text source. Set a
breakpoint on a
Graphline and the preview mesh shows the value bound at that line instead of the finished material. This is the same idea as right-clicking a node in the Material Editor and choosing Start Previewing Node, except the "node" is a line of DreamShaderLang. It reuses the engine's own machinery: the generator publishes a per-source debug table mapping every(line, name)binding to the exactUMaterialExpression/ output / channel-mask it produced, and a transientUPreviewMaterial— sharing the generated material's expression collection, with the probed node wired into its emissive (orMaterialAttributes/FrontMaterialfor those value kinds, and a default-coordinate sample for a texture object) — is recompiled throughFMaterialUpdateContext.UPreviewMaterial::ShouldCachekeeps that recompile to a handful of shaders, exactly as the node-thumbnail path does. A breakpoint on a blank or comment line snaps forward to the next line that binds a value; one set before the source has ever generated is remembered and attaches on the next compile; after a recompile the probe re-resolves automatically and the client is told the line it landed on. Wire protocol:setProbe/clearProbe/probeState. See Editor Tools.
Changed
- The streaming preview now sends raw RGBA8 frames instead of a PNG per frame. A new
encoding: "raw"session reads the render target back and streams the pixels as one self-describing binary frame (a 24-byte header — size, flags, camera, resolved probe line — then the pixels), with no PNG encode on the editor side. A browser/webview client paints them straight onto a canvas. This is most of what a smooth 30–60 FPS stream costs; PNG-encoding a 512² frame per tick was tens of milliseconds of game-thread time. The legacyencoding: "png"path is unchanged for older clients. - Streaming got cheaper when nothing is moving. Identical frames are dropped, and after a few in a
row the render clock backs off to a low idle rate until an edit, a camera/mesh change, or a probe
change re-arms it (a frame rendered while shaders are still compiling never backs off). A
previewControlthat omits a field — includingframeRate— now keeps the current value rather than resetting to 2 FPS, and it can now carrywidth/height/meshso the client can drive the render size and shape without a full re-request. Aforceflag onpreviewMaterialdistinguishes an explicit refresh (regenerate) from a camera/mesh re-request (reuse the last generation).
1.6.0 - 2026-08-15
Language — ten more math builtins
Graphgainsstep,smoothstep,length,cross,asin,acos,atan,atan2,reflectandrefract. Every one of them had a node — or, forreflectandrefract, a four-line definition — and none of them had a spelling, so the only way to writestep(0.5, x)wasUE.Expression(Class="Step", OutputType="float1", Y=0.5, X=x), and the failure when you wrote it the HLSL way wasUnknown Graph function 'step'wrapped inside whichever call the argument belonged to. The builtin surface goes from 19 spellings to 29. See Math Builtins.- Three of the new names do not wire to a pin called
Input, which is the whole reason the mapping is worth stating:StepandArctangent2name their pinsYandX, andSmoothStepnames themMin/Max/Value. Argument order stays HLSL's in every case, sostep(edge, x)wires argument 1 toYand argument 2 toXand still meansx >= edge. lengthandcrossjoindotas the builtins with a fixed return width — 1 and 3 components, authoritative.reflectandrefractare the first builtins that are not one node. Unreal has no expression for either, so they are lowered to the arithmetic HLSL defines them as: four nodes fori - 2 * dot(i, n) * n, and fourteen for refraction including theIfthat returns zero under total internal reflection. Both are exact and both are expensive; where the surrounding code is already HLSL, aFunctionbody still does it in one node.
This widens a reserved namespace. These ten names now shadow user code silently, the same way the
original nineteen do — a Function, property or ShaderFunction named length, step or cross
becomes unreachable from a Graph block with no diagnostic. Existing sources that declare one need
renaming.
Not added, and not addable: matrices. The Unreal material graph has no matrix value type at all, so
mul(M, v) has no Graph spelling regardless of what the DSL does —
UE.Expression(Class="Transform"/"TransformPosition", …) covers space conversions and a Function HLSL
body covers the rest. Still absent but reachable through UE.Expression, each having a node:
exponential, logarithmic, tan, sign, round, trunc and distance. The decompiler is unchanged,
so the new nodes export as generic UE.Expression(Class="…", …) calls rather than round-tripping to the
builtin spelling — the same asymmetry Fmod already has. reflect and refract cannot round-trip at
all, since they leave behind ordinary arithmetic nodes with nothing marking their origin.
Diagnostics — DSHnnnn codes
- Until now the only thing identifying a DreamShader failure was its English wording. All 120 parser
raise sites now carry a code instead:
DSH1xxxpath resolution,DSH2xxxlexer and syntax,DSH3xxxsections and declarations,DSH7xxxproperties, parameters and settings. The code rides onFDreamShaderTextErroralongside theFText. See Diagnostics. - Nothing on the wire changed —
FDreamShaderDiagnosticRecord::Codeand the"code"field indiagnostics.jsonalready existed. - The generator does not raise codes yet: 112 codes are documented, while the full catalogue of all 659 messages, grouped by stage, stays authoritative until the generator's remaining ~561 raise sites are tagged too.
Localization — the editor UI speaks your language; the wire format does not
- Editor-facing text — the DreamShader Gen page, the settings section, slow-task progress, parser and
decompiler diagnostics — moved from
FStringliterals toLOCTEXT/NSLOCTEXT, and the diagnostic records that carry it areFText. Simplified Chinese ships with it:Content/Localization/DreamShader/zh-Hans/DreamShader.locres, loaded through theLocalizationTargetsentry inDreamShader.upluginwith anEditorloading policy.FStringoverloads are kept alongside every converted signature. Thanks to @youli42 — PR #24. diagnostics.json,diagnostics/*.json,bridge.dband the preview WebSocket stay English in every editor culture. The VSCode and Rider extensions parse those, so a translated editor must not change a byte of them.FTextInspector::GetSourceStringis only half an answer: it hands back the source string for a plainLOCTEXT, but for anFText::Formatresult it returns the localized substituted display, so one message site adoptingFText::Formatwould have leaked translated text onto the wire.ToInvariantWireStringinstead replays anFText's historic format data — the source pattern, every argument rendered in the invariant culture, nested formats recursively, number grouping off so12345never becomes12,345— and every JSON / SQLite / WebSocket write goes through it.
Plugin source roots
- Every enabled plugin that ships a
DShaderfolder now contributes its own source root, so a plugin can carry the.dsm/.dsf/.dshfiles that build its materials instead of parking them in the project's tree.Root="Plugin.<Name>"has been able to write assets into a plugin since1.2.0; the source half was missing. Discovery, the dependency graph and generate-all pick plugin roots up with no further configuration. See Project Layout. - Project Settings ▸ DreamPlugin ▸ Dream Shader ▸ Paths ▸ Scan Plugin Source Directories
(
bScanPluginSourceDirectories, default on) turns the plugin scan off. - A plugin-root file defaults to its own plugin's mount point. A
.dsmunderPlugins/MoonToon/DShaderwith noRoot=attribute now generates into/MoonToon, not/Game— source and asset stay in the plugin that ships them. Only an absent or whitespace-onlyRootis defaulted, soRoot="/"opts back into/Game. - Imports never cross roots. A file resolves its imports against its own root and that root's
Packagesfolder only. Two plugins shipping the same relative path can no longer shadow one another, and disabling a plugin cannot silently change what another root's import means. - Root-qualified imports —
import "Plugin.MoonToon:Shared/Toon.dsh";— are the one way to cross a root deliberately. The qualifier isProject,Plugin.<Name>orPlugins.<Name>(/spells the same as.), matched case-insensitively. The:is load-bearing:Plugin.MoonToon/Shared/Common.dshcould not be told apart from a relative path through a folder of that name. See Imports. - The source-directory watcher registers one watch per root,
DreamShader.code-workspacelists onefoldersentry per root, and the DreamShader Gen page labels a plugin-root file with its root name in the row subtitle — typing a plugin's name filters to everything it ships.
Editor and generation
- Graph layout runs on in-memory materials. Interactive compiles are memory-only, and the placement
pass used to be skipped for them outright — so the graph you saw after a save was not a badly laid out
graph, it was an unlaid out one: the tall single column of construction coordinates, wires strung
across it. Project Settings ▸ … ▸ Compiler ▸ Lay Out In-Memory Graphs
(
bLayoutInMemoryGraphs, default on) turns it back off. See Project Settings. - Automatic layout places nodes at their real size. Every node used to be assumed
320 × 150and spaced on a fixed420 × 220grid, so nothing that draws taller than 220 fitted — aTextureSample's preview thumbnail alone is 106, aCustomnode grows a row per pin — and those nodes overlapped their neighbours and pushed out through the comment box meant to contain them. Sizes are now estimated from what the node widget actually assembles. - Long edges get lanes, and chains come out straight. An edge spanning several ranks now reserves a dummy slot in each column it crosses. Crossing reduction only ever compares neighbours one rank apart, so without lanes it could not see those edges at all — which is what let a graph read as a ball of wire even though each column, taken on its own, was tidy. Placement then runs four straightening passes, each repairing the column with an isotonic regression.
- Blocks pack into columns instead of one stack. A material with several connected outputs used to
become a ribbon tens of thousands of units tall. Blocks are now packed into columns against a height
budget of
max(2400, sqrt(totalArea / 1.6)). - The DreamShader Gen page reads diagnostics from the bridge, not by re-parsing the
diagnostics.jsonthe bridge had just written. One consequence worth knowing: those records live for the editor session, so the page no longer shows errors left over from a previous session before you compile again.
Fixed
- The plugin builds again on UE 5.5 and 5.6. Five UE 5.7 APIs had been used without a gate, so the
plugin compiled only on the engine it was written against. All five now route through
DreamShaderVersionCompat.h:Materials/MaterialParameters.h(5.7 and later),UMaterialExpression::ShouldShowPreview()(5.7),UMaterialExpressionScalarParameter::ControlTypeand its two neighbours (5.7),UMaterialExpressionCustomOutput::GetInputValueType(5.6), and six engine expression classes —SceneDepth,SceneColor,ObjectRadius,ObjectBounds,PerInstanceRandom,PerInstanceFadeAmount— that areUCLASS()with no export macro and whoseStaticClass()does not resolve from a plugin before 5.6. That last one is anLNK2019, not a compile error: it passes every compile-time check and only a fullRunUAT BuildPluginsees it. Below 5.6 the class is looked up by script path instead, so theUE.*builtins behave the same. - Two missing includes that unity builds had been papering over (
UObject/Package.h,Engine/EngineTypes.h), which only a non-unity compile exposes.
Tools
- The editor bridge answers now, and says whether it is alive. It had an inbound half and nothing
else: a request produced no reply, no error file and no log line — a malformed one simply vanished —
and a client's only way to guess whether an editor was running at all was to look for
bridge.dband hope. The bridge now publishesBridge/status.json(protocol, pid, project, plugin version,busy/busyAction,lastResult, heartbeat), rewritten every 2 s and deleted on shutdown so a missing file means "not running" definitively rather than "timed out", and answers any request carrying arequestIdinBridge/Responses/<requestId>.jsonwithok,durationMs,messageand this compile's diagnostics. Apingaction was added; bad scopes, missingsourceFileand unknown actions are now error responses instead of silent no-ops. See Editor Tools. .skill/build-plugin.ps1—RunUAT BuildPluginacross a list of engine roots, onePASS/FAILline each, exit code = the number that failed. This is what caught the five ungated newer-engine APIs above; an editor build against one engine cannot, and neither can any compile-time check, because one of the five is a link error.Tools/Localization/localization_lint.ps1— checksLOCTEXT_NAMESPACEdefine/undef pairing, rejects it in headers, and flags literals that gather cannot see, with anI18N-EXEMPTescape for text that is deliberately not display text.
1.5.1 - 2026-08-02
Documentation and tooling only. No plugin code changed, so a project on 1.5.0 needs no migration.
Added
.skill/— an agent skill set for DreamShaderLang, in the Claude CodeSKILL.mdformat:dream-shader-create(a description becomes a.dsmthat provably compiles),dream-shader-optimize(decompiler output becomes a source a human would write),dream-shader-decompile,dream-shader-verifyanddream-shader-diagnose..skill/dsc.ps1— a headless driver around-run=DreamShader. It resolves the engine from the.uproject'sEngineAssociation, finds the project by walking up, de-duplicates the doubledLogInitecho of everyLogDreamShaderline, and classifies each asset the run wrote against git.-CleanNewthen deletes exactly the untracked ones and prunes the emptied folders..skill/sync-skills.ps1— publishes the tree into.claude/skills, rewriting the relativeDocs/links and the driver path against the destination.-Checkexits1on drift.
Changed
- Both READMEs restructured around the reference manual. The sections that had become abridged copies of
Docs/pages — Properties, Graph, MaterialAttributes, Substrate, Material Layers, VirtualFunction, Configuration, Release — now link to the page that owns them, which is also the page that gets maintained. 459 → 279 lines, and the two languages are kept structurally identical.
Fixed
- The release archive now ships
Shaders/,README.zh-CN.mdand.skill/. Up to1.5.0the packaging step copied a seven-item allowlist and skipped anything missing silently, so an archive install had noShaders/DreamShaderBuiltins.ushfor the/Plugin/DreamShadervirtual shader directory to resolve against.
1.5.0 - 2026-08-02
The release that unifies compilation. The two old backends collapse into one ThinCustom path,
generated materials become memory-only in the editor, and a new Material Content Browser tab is
where you look at them. The language picks up optional section =, Group() scopes and
Slider().
Language (DreamShaderLang 1.5)
- Section
=is now optional: writeProperties { … },Settings { … }andGraph { … }without the assignment. Both spellings stay valid — see Sections. Properties Group("Name") { … }scopes a group onto every parameter it contains. Groups nest and compose, soGroup("Outer") { Group("Inner") { … } }yieldsOuter|Inner— see Metadata and Groups.Slider(min, max)shorthand sets a scalar parameter's UI range. It expands to the reflectedSliderMin/SliderMaxproperties and is the one metadata entry written without an=.- Asset paths can follow
=directly, and bare quoted paths are accepted — see Asset References. - Single-output functions can be used as return values (
x = Fn(…)), andGraphbuiltins now match theFunctionpath:fract,modandfmod— see Math Builtins. - Live preview streaming keeps the editor and language-server previews in sync while you type.
true/falseare graph literals and materialize asStaticBoolnodes, so anopt StaticBool X = falseinput default generates the Preview-pin node Unreal requires — it ignoresPreviewValuefor static-bool inputs.StaticBoolresolves as a one-component type at call sites.- Texture parameter types whose token carries no dimension —
TextureObjectParameter— take their dimension from the assigned default asset, so aTexture2DArray,TextureCubeorVolumeTexturedefault is accepted. Explicit tokens such asTexture2DandTexture2DArraystill validate strictly.
Backend — one unified compilation path
- The
Graphand the experimentalInstancebackends are collapsed into a single ThinCustom path: DreamShaderLang compiles to a real node graph on a hidden baseUMaterial, wrapped by a thinUDreamShaderMaterialInstance. The engine compiles and enumerates the material natively, so Substrate, static switches, virtual textures, MaterialAttributes and cook correctness all come from the real graph. See Backend. - Bit-identical SM6 render parity with the previous
Graphbackend, verified across Unlit, textured, and DefaultLit MaterialAttributes cases. - The hidden base is a subobject of the instance — one asset, one package, invisible in the
Content Browser, with no separate
MB_DreamThinBase_*sibling and no cross-package parent import to lose at cook. See In-memory Materials. - A single Default Compiler Backend project setting replaces the old In-Memory toggle. See Project Settings.
Deprecated since 1.5.0
Use ThinCustom instead.
Backend = "Instance" and DefaultBackend = Instance are retained as aliases for
ThinCustom. The legacy graphless instance backend is retired; there is no runtime Instance
backend left. The spelling is kept for one deprecation window so existing sources keep compiling,
and it produces no diagnostic.
Editor — Material Content Browser
- New DreamShader Material Content Browser tab under Tools ▸ DreamShader, with two pages:
Project browses, filters and inspects every material and material instance under
/Game, including the full inheritance chain; Dream Shader Gen lists the sources with live preview, search, filters, compile-all, and load-time error surfacing. See Editor Tools. - Create material instances from any material through a folder picker, and materialize in-memory (preview-only) materials to disk on demand.
- Content Browser context-menu actions, and a toggle to show or hide DreamShader's memory-only materials.
Decompiler
- Faithful round-trip for Substrate materials and renamed graph channels. The exporter derives channel swizzles from the write mask rather than the channel name, so recompiled materials match the source bit-for-bit. See Decompiler.
Decompiling a hand-authored material and generating it back could fail on graph shapes Unreal
itself accepts. Found on LGUI's LexUI_ImageAndFont, LexUI_RectBlock and MF_LexUI_SDF_Font.
- Switch-style nodes —
StaticSwitch,FeatureLevelSwitch,QualitySwitch,ShadingPathSwitch,VertexInterpolatorand friends — report no output value type, so the "assume float4" fallback oversized them and everything downstream. AnAppendVectorfed by a float3 material-function output was emitted asfloat5(...); appends are now clamped to a float4, with a warning when a count still disagrees. VertexColoris emitted as float4 so the alpha pin's swizzle is valid. It used to be typed from the RGB pin and produce.aon a three-component value.- An input's own channel mask now replaces the connected pin's mask instead of stacking on it,
so
.rgb.ano longer appears when a graph wires the RGB pin but masks alpha. - A
StaticBoolfunction input keeps theStaticBooltype token.booldeclares a scalar pin, so the input used to come back as a float and reject every static-bool value passed to it. - Comment,
#Regionand description text carrying newlines or tabs is escaped, so a multi-line comment no longer splits the directive across lines. - A Custom node's additional outputs are declared on every emission of that node, and reading
one no longer rewrites the node's own return type. Previously the emission that did not select the
extra output produced a node without it, and the code body assigning to it failed at
shader-compile time with
use of undeclared identifier— long after generation reported success.
Fixed
- Cook: assets are materialized on the cook director only, and a generation error now fails the cook instead of shipping a stale asset.
- Generation refuses to overwrite assets DreamShader did not generate, and pre-validates graph syntax before clearing the target material.
- Generated-include identity hashes the project-relative source path; stored source paths are project-relative and no longer carry a generated-at timestamp.
- Runtime builds: guarded the editor-only
UEnum::HasMetaDatacall so non-editor and Shipping (store) builds compile (#12). - Bridge: adopted
FCoreDelegates::GetOnPostEngineInitfor UE 5.8, and constrained Clean Generated Shaders toIntermediatewith per-file deletes.
Compatibility
- Unreal Engine
5.3through5.8(Win64).
Source snapshot - 2026-06-10
Landed in the source tree between 1.4.0 and 1.5.0.
Automation baseline
- Added a DreamShader automation-test baseline covering minimal material parsing, minimal material
generation,
.dsfplusimportgeneration, Substrate material generation, source-hash skip, and a commandlet single-source compile smoke test. - The tests write temporary
.dsm/.dsf/.dshfiles underDShader/Tests/Automation, generate temporary assets under/Game/DreamShaderTests/Automation/…, and clean up after the run. source hash is unchangedis the stable assertion text for an unchanged source file skipping a duplicate generation.- Added the explicit bridge kill switch
-NoDreamShaderEditorBridgefor automation runs, so bridge startup scans stay out of the test log.
1.4.0 - 2026-06-06
Compatibility
- Added Unreal Engine
5.3through5.7compatibility coverage. - Verified single-plugin
RunUAT BuildPluginbuilds for UE5.3,5.4,5.5,5.6and5.7on Win64. - UE
5.7remains the active development target; UE5.3and5.4may need the MSVC14.38toolchain on Windows.
Substrate
- Completed the Substrate generation path:
Substrategraph values, theBase.FrontMaterialoutput binding, and theSubstrate.*wrappers. Substrate itself requires UE 5.4 or newer with Substrate enabled in the project — see Substrate Nodes. - The generator, the decompiler, type propagation and the editor manifest all recognize the Substrate type and its wrappers.
Material Preview
- The editor bridge can resolve a
.dsmsource, generate a preview material, and writeSaved/DreamShader/Bridge/preview.jsonplusPreview/*.png. - Added a local WebSocket preview service, listening on
127.0.0.1:17864by default, which feeds results and continuous frames to the VSCode preview panel.
1.3.9 - 2026-05-29
Maintenance
- Plugin version metadata updated to
1.3.9. - README, release notes and documentation references synchronized with the current language capabilities.
1.3.8 - 2026-05-25
Texture Support
- Added
VolumeTextureproperty parsing, code generation and default-texture handling. - Texture object subtypes are preserved during code generation, so
Texture2D,Texture2DArrayandVolumeTextureinputs reach the generated HLSL with the correct Unreal texture type.
Plugin Cleanup
- Removed built-in shader library path support from the project settings and the documentation.
1.3.7 - 2026-05-18
Decompiler
- Generic
UE.Expression(...)decompilation exports reflected literal properties, so unsupportedMaterialExpressionnodes retain more editable state. TextureSampleParameter2Dnodes with connected graph inputs — UV coordinates, for instance — are exported as graph expressions instead of plainPropertiesdeclarations.- Fixed decompilation of
MaterialExpressionCustomnodes with dynamic named inputs and custom output-type metadata.
Performance
- Improved import performance for very large decompiled materials: less per-node package dirtying, throttled progress-text updates, and automatic layout skipped on large generated graphs.
1.3.6 - 2026-05-12
Build Fixes
DreamShaderSettings.hincludesMaterialDomain.hexplicitly, so projects that include the settings header directly resolveEMaterialDomainreliably.
1.3.5 - 2026-05-11
ShaderFunction Calls
Graphaccepts statement-style multi-outputShaderFunctionandVirtualFunctioncalls: positional inputs first, output target variables after.
Dream Shader Function Files
- Added
.dsfDream Shader Function files for reusable generatedShaderFunctionassets. .dsmand.dsffiles can import.dsffiles, so generated functions are reusable across DreamShader sources.- Added
.dsfsource discovery, dependency tracking, and the VSCode workspace file association.
Decompiler
- Added Content Browser export actions:
UMaterialto.dsm,UMaterialFunctionto.dsf. - Decompiled files are written under
DShader/Decompiled/MaterialsorDShader/Decompiled/Functionswith unique file names. - Common constants, parameters, arithmetic nodes, swizzles, texture samples, Custom nodes and
MaterialFunction calls are exported as DreamShader graph text; less common reflected nodes fall
back to
UE.Expression(...).
1.3.4 - 2026-05-11
Output Initializers
Outputsaccepts initialized declarations such asvec3 Color = Tint;.- A
Shaderblock can use initialized output declarations with an emptyGraph = {}block.
1.3.3 - 2026-05-11
Graph Swizzles
- Fixed vector property component counts, so
vec2/vec3properties bind throughRG/RGBinstead of always usingRGBA. - Fixed non-sequential swizzles such as
.gbrby generating explicitComponentMaskandAppendVectornodes.
1.3.2 - 2026-05-11
Material Function Generation
- Plugin version metadata updated to
1.3.2. - Generated
ShaderFunctioninput and output IDs are preserved across regeneration, so existingMaterialFunctionCallnodes in ordinary Unreal materials keep their connections. - The Graph and Custom/HLSL generation paths skip unused generated property nodes.
- Improved generated node placement, and avoided Unreal's full automatic layout pass on DreamShader-generated material graphs.
- Fixed a crash when regenerating an opened material function asset whose expressions were still rooted by the editor.
1.3.1 - 2026-05-09
Function Calls
- Single-output
FunctionandGraphFunctioncalls can be used as value expressions, for exampleColor = Texture::Sample2DRGB(BaseTex, UV0);— see Calls. - Multi-output
FunctionandGraphFunctioncalls still require explicit out variables, for exampleTexture::Sample2D(BaseTex, UV0, Color, Alpha);.
Graph Functions
- Added top-level and namespaced
GraphFunctionblocks for reusable HLSL Custom-node logic. GraphFunctionremains HLSL, butUE.*calls inside its body are converted into material nodes and passed into the Custom node as generated inputs.- Added GraphFunction argument validation, recursive-call detection, and explicit out-variable writeback.
1.3.0 - 2026-05-08
Shader Layer Functions
- Added top-level
ShaderLayer(Name="...", Root="...")andShaderLayerBlend(Name="...", Root="...")blocks. - Generated layer assets use Unreal's native
UMaterialFunctionMaterialLayer/UMaterialFunctionMaterialLayerBlendclasses. ShaderLayer/ShaderLayerBlendreuse the existingProperties,Inputs,Outputs,SettingsandGraphsections.- Added validation that a Shader Layer block outputs exactly one
MaterialAttributesvalue, and that a Shader Layer Blend block declares at least twoMaterialAttributesinputs. - Vector parameter properties keep their RGBA output available in
Graph, so.a/.wreads alpha and assignments to lower component counts use the leading channels automatically.
Deprecated since 1.3.0
Use ShaderLayer instead.
MaterialLayer and MaterialLayerBlend remain compatibility aliases and emit warnings. New source
should use ShaderLayer and ShaderLayerBlend.
1.2.10 - 2026-05-08
VSCode MaterialExpression Manifest
- Added editor-side export of reflected
UMaterialExpressionmetadata toSaved/DreamShader/Bridge/material-expressions.json. - The manifest is refreshed on editor bridge startup and when the DreamShader VSCode workspace is opened.
- Exported metadata covers expression class names, editable reflected properties, expression
inputs, output pins, and inferred DreamShader
OutputTypehints. - The release workflow downloads the latest
dreamshader-language-supportGitHub Release assets and attaches them to DreamShader releases.
1.2.8 - 2026-05-05
Project Settings and Editor Entry Points
- Plugin version metadata updated to
1.2.8. - The
DreamShader.uplugindocumentation link now points athttps://lang.64hz.cn/, and the support link at the GitHub home page. - The Unreal Project Settings category moved from
PluginstoDreamPlugin, with the section displayed as Dream Shader and described asDream Shader Settings— see Project Settings. - Added the
OpenInNewWindowsetting under theEditorcategory. On by default, opening the DreamShader VSCode workspace from Unreal opens a new window; off,--reuse-windowis appended and an existing VSCode window is reused.
1.2.7 - 2026-05-05
Unreal 5.7 Compatibility
- Plugin version metadata updated to
1.2.7. - The generator picks up the header dependencies for
UMaterialExpressionFunctionInput/UMaterialExpressionFunctionOutput, matching the newer Unreal material-function node build environment. - The Moon Engine specific
MooaEncodedAttribute0throughMooaEncodedAttribute4outputs are now wrapped inMOON_ENGINEconditional compilation, so ordinary engine builds no longer depend on those custom material attributes. - This release does not change
.dsm/.dshsyntax; it is output targets and engine compatibility.
1.2.6 - 2026-04-30
ShaderFunction Properties
- Added a
Propertiessection toShaderFunction, declaring property and helper nodes local to the generated material function. - Added
constproperty declarations for scalar, vector and texture helper nodes that are not externally adjustable parameters. ShaderFunctionInputspreview defaults can reference the same function'sProperties, for exampleopt Texture2D BaseColorTex = PreviewTex;.
1.2.5 - 2026-04-30
Material Attributes
- Added
MaterialAttributesas a graph value type forShader,ShaderFunctionandVirtualFunctionsignatures. - Added struct-like member writes such as
Attrs.BaseColor = Color;andAttrs.Roughness = Roughness;. - Added
Base.MaterialAttributes = Attrs;output binding, which enables Use Material Attributes on the generated material automatically. MaterialAttributesvalues can be returned from generated or virtual material functions and passed throughGraphassignments.
1.2.4 - 2026-04-30
Parameter Reflection
- The documented comma-style metadata suffix was replaced with a semicolon-separated trailing reflection block on declarations.
- Parameter reflection blocks can set any reflected
UMaterialExpressionproperty exposed by the generated parameter node. - The basic
float, vector and texture shorthand declarations use the same reflection path as explicit parameter node declarations. - Texture sample parameters can configure reflected properties such as
SamplerType,SamplerSource,MipValueMode,AutomaticViewMipBias,ConstCoordinateandConstMipValue.
1.2.3 - 2026-04-29
Parameters
- Added declaration metadata
[Group="...", SortPriority=32, Description="..."]for materialPropertiesand for function input/output declarations. - Added explicit parameter node declarations:
ScalarParameter,VectorParameter,TextureObjectParameter, the texture sample parameter family,StaticBoolParameterandStaticSwitchParameter— see Property Types. - Added
StaticSwitchParametergraph calls, for exampleUseDetail(True=detailColor, False=baseColor). - Added
UE.CollectionParam(Collection=Path(...), Parameter="...")for Material Parameter Collection reads.
Function Defaults
- Added
optinputs forShaderFunctionandVirtualFunction. - Added the
defaultcall argument for optional material function inputs, preserving Unreal FunctionInput preview defaults. - Generated
ShaderFunctionassets write input and output descriptions and sort priorities to the FunctionInput / FunctionOutput nodes. - VirtualFunction copy, create and sync emit optional inputs, preview defaults and pin metadata when available.
1.2.2 - 2026-04-29
VirtualFunction Workflow
CreateVirtualFunctionreuses the existing declaration for the selected material function instead of creating duplicate.dshfiles.- When a matching declaration already exists, the material function
DreamShadermenu showsOpenVirtualFunctionandCopy Virtual Function Referenceinstead of the create and copy-definition actions. OpenVirtualFunctionopens the existing declaration in VSCode and jumps to the declaration location where possible.- Added startup validation and refresh for
VirtualFunctiondeclarations underDShader, reporting missing sourceUMaterialFunctionassets and updating changed signatures.
Import Compatibility
import "File.dsh"works with or without a trailing semicolon in the Unreal generator import pass.
1.2.1 - 2026-04-29
Editor Workflow
- Replaced the single material function toolbar action with a
DreamShaderdropdown menu. - Added
CopyVirtualFunction,CreateVirtualFunctionandCopyVirtualFunctionCallto the material function editor toolbar and the material function asset context menu. CreateVirtualFunctionwrites a.dshdeclaration under the configuredDShader/VirtualFunctionsdirectory and opens it in the default external editor.CopyVirtualFunctionCallcopies a ready-to-pasteGraphcall using the generated input names and the first output.- Added Open Dream Shader Workspace (VSCode) to the editor Tools menu and the DreamShader toolbar
section. It writes
DShader/DreamShader.code-workspace, opens it in VSCode when available, and falls back to the default editor or Notepad.
Release
- Added a GitHub Actions release workflow that packages the plugin source and publishes a GitHub Release from version tags or manual workflow dispatch.
1.2.0 - 2026-04-28
VirtualFunction
- Added
VirtualFunction(Name="...")declarations for existing UnrealUMaterialFunctionassets. VirtualFunctioncalls work fromGraphlikeShaderFunctioncalls, without generating or overwriting the referenced asset.Options.AssetsupportsPath(Game, "..."),Path(Engine, "..."),Path(Plugin.PluginName, "...")/Path(Plugins.PluginName, "..."), and full Unreal object paths.- Added material function context-menu and Material Editor toolbar actions that copy a complete
VirtualFunctiondeclaration with inputs, outputs and options.
Asset Roots
Root="Plugin.PluginName"maps to the project plugin content root, physically saving generated assets under[Project]/Plugins/PluginName/Content.Plugins.PluginNameandPlugins/PluginNameremain compatibility spellings.
Tooling
- The VSCode extension picked up
VirtualFunctionhighlighting, completion, hover, snippets, signature help and diagnostics. - Project content plugin names are completed inside
Path(Plugins.)as well.
1.1.4 - 2026-04-28
Asset Generation
Root="Plugins.PluginName"/Root="Plugins/PluginName"are supported as compatibility spellings, resolving identically toRoot="Plugin.PluginName".- This avoids
Plugins.MoonToonbeing treated as an ordinary/Game/Plugins_MoonToonsubdirectory — see Asset Paths.
1.1.3 - 2026-04-28
Asset Generation
- Clarified that
Root="Plugin.PluginName"targets an enabled project content plugin only. Plugin.MoonToonresolves to the UE package root/MoonToon, physically saved under[Project]/Plugins/MoonToon/Content.- The generator validates that the target plugin sits in the project
Pluginsdirectory, is enabled, can contain content, and has aContentdirectory.
1.1.2 - 2026-04-28
Language and Generation
- Added the
Roottop-level attribute toShaderandShaderFunction, selecting the generated asset's root path. Rootdefaults toGameand also acceptsPlugin.PluginName, saving the generatedUMaterialorUMaterialFunctioninto an enabled project content plugin root.- When a
ShaderFunctionis called fromGraph, the generatedUMaterialFunctionasset is resolved and loaded through that function's ownRoot.
Documentation
- Updated the asset generation notes in the plugin README, the language reference and the site.
1.1.1 - 2026-04-26
Fixed
- Fixed a
Functioncall with severaloutparameters inGraph, where the Custom node's trailing output pins received no value. - The first
outof a multi-outputFunctionstill comes back through the Custom node's mainreturn; the second and lateroutparameters now generate explicit assignments writing the__ds_*_outNtemporaries back to the matching Additional Output, for exampleOutput_ToonA = __ds_xxx_out1;.
Compatibility
- A patch release. No
.dsm/.dshchanges are required.
1.1.0 - 2026-04-22
Language and Graph
- Renamed the
Code = { ... }block in materials and material functions toGraph = { ... }, separating the graph DSL from the HLSL helper layer. - Added basic
if/elsesupport toGraph— see Control Flow. - Improved output expression handling: an output binding records source text rather than only a variable name, allowing more flexible parsing of graph outputs.
- Improved type conversion and generic
UE.*MaterialExpression argument handling, reducing friction when patching nodes in by hand.
Function and Generation
- Added
Function SelfContainedandFunction Inline, embedding helper dependencies in the Custom node so generated materials can be handed to projects without the DreamShader plugin installed. - An ordinary
Functionstill generates a.ushhelper include, referenced from the Custom node. - Added a cleanup workflow for generated shader files, reducing leftover historical includes.
Packages and Workflow
- Added the Package system, distributing and installing reusable
.dshshared libraries through GitHub — see Packages. - Added the import dependency graph: saving a shared
.dshfinds the affected.dsmfiles and refreshes them incrementally. Later versions extend this to.dsfdependencies. - Cached the config directory, cutting the cost of repeatedly parsing project settings.
Diagnostics and Compatibility
- Diagnostics carry stage, platform and quality context, separating parse, generation and material compile problems — see Diagnostics.
- Removed the default mapping for the Toon shading model, so generated results do not unexpectedly depend on a project-custom shading model.
- Updated the README, language reference and example documentation to match.
1.0.0 - 2026-04-21
Initial Version
- The
.dsm/.dshfile model: Unreal materials and shared helpers described as text sources. Shader(Name="...")generates aUMaterial;ShaderFunction(Name="...")generates aUMaterialFunction.Properties,Settings,Outputsand the early graph-building syntax.- HLSL-style
Functionhelpers, generating Custom node code. - Baseline documentation and examples, as the first public DreamShaderLang release.