DreamShaderLang
Tooling

VSCode and Rider

The language extensions, the generated DreamShader.code-workspace, and the bridge artifacts the two sides exchange.

DreamShaderLang ships as two independent products: the Unreal plugin, and a language extension for your editor. They never call each other directly — they exchange files under <Project>/Saved/DreamShader/Bridge/ and one loopback WebSocket. Knowing which side owns what saves a lot of time when something does not work.

SideOwns
Unreal plugincompilation, diagnostics, the bridge artifacts, the preview renderer, DreamShader.code-workspace
Editor extensionsyntax highlighting, completion, hovers, navigation, the preview panel and its camera, package install/update, the package manifest and lock file

Which side implements what

SurfaceImplemented byNotes
Menus, toolbar, context menus, the browser tabpluginEditor Tools
Auto-compile-on-save, the diagnostics storepluginwrites three diagnostic sinks
Preview rendering (the PNG frames)pluginthe renderer, the mesh set and the clamps
Preview camera control, pitch clamping, frame acknowledgementextensionthe plugin applies no pitch clamp of its own
DecompilerpluginDecompiler
.dsm / .dsh / .dsf highlighting, completion, hoversextensionfed by the manifests the plugin exports
DreamShader.code-workspacepluginrewritten on every Open Dream Shader Workspace
dreamshader.package.json, dreamshader.lock.json, install/update commandsextensionno plugin C++ reads either filePackages
DShader/Packages creation, import resolution, auto-compile exclusionpluginPackages

The extensions

Neither extension ships inside the plugin. Install one separately.

EditorRepositoryProvides
VSCodeTypeDreamMoon/dreamshader-language-supportSyntax highlighting, snippets, completion, Go to Definition, Find References, Hover, Signature Help, local diagnostics, Unreal bridge diagnostics, package commands, quick templates, the material preview panel
JetBrains Ridertsdaer/dreamshader-language-support.dsm / .dsf / .dsh file types, grammar and PSI parsing, highlighting, completion, navigation, diagnostics, Unreal Bridge integration, semantic tokens, inlay hints, package tools
CapabilityVSCodeRider
File type recognitionyesyes
Syntax highlightingyesyes
Parser modelparser-based language serviceJetBrains PSI parser
Completion, hover, signature helpyesyes
Go to definition, find referencesyesyes
Local diagnosticsyesyes
Unreal bridge diagnosticsyesyes
Inlay hints / semantic tokensyesyes
Material preview panelyesplugin-dependent
Package commandsyesplugin-dependent
Authoring templatesyesplugin-dependent

The release workflow attaches the latest VSCode extension assets to each plugin GitHub release, so the two versions stay roughly in step. Extension-side settings — a project-root override, a preview frame rate, package store index URLs — are declared by the extension, not by UDreamShaderSettings, and the extension's own repository is authoritative for them.

The generated workspace

Tools ▸ DreamShader ▸ Open Dream Shader Workspace (VSCode) — and the toolbar button of the same name — writes one file and launches an editor on it. since 1.2.1

AspectValue
Path<SourceDirectory>/DreamShader.code-workspace<Project>/DShader/ by default
EncodingUTF-8 without BOM, pretty-printed by Unreal's JSON writer (tab indentation)
PlatformWindows only — discovery uses Windows environment variables, a ;-separated PATH, cmd.exe and notepad.exe

The source directory is created if it does not exist. The whole file:

{
	"folders": [
		{
			"name": "DreamShader Source",
			"path": "."
		}
	],
	"settings": {
		"files.associations": {
			"*.dsm": "dreamshaderlang",
			"*.dsh": "dreamshaderlang",
			"*.dsf": "dreamshaderlang"
		}
	}
}

Every key the writer emits. There are no others, and nothing is conditional.

KeyValuePurpose
folders[0].nameDreamShader Sourcedisplay name of the single workspace folder
folders[0].path.the folder holding the workspace file — <SourceDirectory> itself
settings["files.associations"]["*.dsm"]dreamshaderlanglanguage id for material sources
settings["files.associations"]["*.dsh"]dreamshaderlanglanguage id for headers
settings["files.associations"]["*.dsf"]dreamshaderlanglanguage id for function sources since 1.3.5

The file is rewritten from scratch on every invocation. The writer serializes a fixed object; it never reads, merges or preserves what was there. Any launch, tasks, extensions or extra settings entries you added by hand are destroyed the next time the command runs. Keep per-user configuration in a different .code-workspace file, or in <SourceDirectory>/.vscode/settings.json — the command touches neither.

What the command does, in order

StepActionOn failure
1Re-export material-expressions.jsonlogged, the command continues
2Re-export settings.jsonlogged, the command continues
3Re-export substrate-builtins.jsonlogged, the command continues
4Write DreamShader.code-workspacetoast + warning, the command aborts
5Launch an editor on the workspace filetoast + warning

Steps 1–3 rewrite the same three manifests the bridge writes at editor startup, so an extension installed a minute ago sees current data without an editor restart. See Bridge artifacts.

Launch fallback chain

The first mechanism that succeeds wins; the rest are not attempted.

OrderMechanismDetail
1VSCodethe first discovered executable that yields a valid process handle. .cmd / .bat candidates run through %ComSpec% (falling back to C:/Windows/System32/cmd.exe) with /C, hidden; .exe candidates are spawned directly
2Shell default applicationLaunchFileInDefaultExternalApplication with the Edit verb — whatever is registered for .code-workspace
3Notepad%SystemRoot%\System32\notepad.exe if it exists, otherwise bare notepad.exe
(none)failure toast and a warning in the log

VSCode executable discovery

Probed in this exact order. Only paths that exist as files are kept, and duplicates are dropped.

OrderCandidate
1%LOCALAPPDATA%\Programs\Microsoft VS Code\Code.exe
2%LOCALAPPDATA%\Programs\Microsoft VS Code\bin\code.cmd
3%LOCALAPPDATA%\Programs\Microsoft VS Code Insiders\Code - Insiders.exe
4%LOCALAPPDATA%\Programs\Microsoft VS Code Insiders\bin\code-insiders.cmd
5%ProgramFiles%\Microsoft VS Code\Code.exe
6%ProgramFiles%\Microsoft VS Code\bin\code.cmd
7%ProgramFiles(x86)%\Microsoft VS Code\Code.exe
8%ProgramFiles(x86)%\Microsoft VS Code\bin\code.cmd
9for each ;-separated PATH entry, in PATH order: code.cmd, code.exe, Code.exe, code-insiders.cmd, Code - Insiders.exe

There is no setting that names a VSCode executable. A non-standard install is reachable only by putting it on PATH.

Which launcher an action uses

AspectValue
SettingOpen In New WindowbOpenInNewWindow, category Editor
Defaulttrue
Effectwhen false, --reuse-window is appended to the VSCode command line. When true, no flag is passed and VSCode applies its own default

bOpenInNewWindow is consulted by the workspace launcher only. Every other DreamShader action that opens a file in VSCode uses a separate launcher that always passes --reuse-window -g "<path>:<line>:<column>", regardless of the setting.

ActionLauncherWindow behaviour
Open Dream Shader Workspace (VSCode), menu and toolbarworkspace launcherhonours bOpenInNewWindow
Open source (Material Content Browser, Gen page)file launcheralways --reuse-window
OpenVirtualFunction (asset context menu)file launcheralways --reuse-window, positioned at the declaration's line and column
Export DSM / Export DSF post-export openpreferred-editor chainalways --reuse-window when VSCode is used

The file launcher clamps line and column to 1 or greater, and its own fallback chain is VSCode → shell default application (Edit verb) → Notepad, the same shape as the workspace chain.

Diagnostics

Toast text and log text differ; both are listed. Runtime substitutions are written {Placeholder}.

ToastLogCause
DreamShader failed to create workspace: {Error}Warning — Failed to create DreamShader workspace: {Error}the workspace file could not be written; {Error} is one of the three writer errors below
Opened DreamShader workspace in VSCode: {Path}Display — same texta VSCode candidate launched
Opened DreamShader workspace: {Path}Display — Opened DreamShader workspace with the default editor: {Path}the shell default application launched
Opened DreamShader workspace in Notepad: {Path}Display — same textNotepad launched
DreamShader could not open workspace: {Path}Warning — Failed to open DreamShader workspace: {Path}every mechanism failed; the file was still written
Writer errorCause
DreamShader source directory is empty.the resolved source directory normalized to an empty string
Failed to create DreamShader source directory '{Path}'.the source directory did not exist and could not be created
Failed to write DreamShader workspace file '{Path}'.the file could not be saved — read-only, locked, out of space

What an extension consumes

Every artifact an extension reads or writes lives under <Project>/Saved/DreamShader/Bridge/, plus the loopback WebSocket endpoint.

ArtifactDirectionContents
Requests/*.jsonextension → editorrecompile, clean and one-shot preview commands
diagnostics.jsoneditor → extensionall current diagnostics, grouped by source file
diagnostics/index.json + diagnostics/<md5>.jsoneditor → extensionthe same data sharded per file, for incremental reads
bridge.dbeditor → extensionSQLite mirror of the diagnostics and the three manifests
material-expressions.jsoneditor → extensionreflected UMaterialExpression catalogue for UE.Expression completion since 1.2.10
settings.jsoneditor → extensionShadingModel / BlendMode / MaterialDomain alias tables
substrate-builtins.jsoneditor → extensionSubstrate.* catalogue with snippets; supported: false below UE 5.4
preview.json + Preview/*.pngeditor → extensionresult manifest and image for a one-shot preview
ws://127.0.0.1:17864bidirectionalstreaming preview with orbit control

bridge.db is write-only from the plugin's side and is not durable state: it is deleted on bridge startup and on shutdown, and every writer replaces its whole table inside a transaction. Nothing in the plugin ever reads a row back. Treat it as a query-friendly mirror of the JSON, valid only while the editor is running — never as a place to store client state.

Request files

AspectValue
Directory<Project>/Saved/DreamShader/Bridge/Requests/
Discovery*.json, files only, non-recursive
Poll interval0.1 s
Consumptionevery discovered file is deleted at the end of its loop iteration

The filename is irrelevant; only the JSON contents matter. action and scope are both matched case-insensitively. There are exactly four actions:

actionRequired fieldsEffect
recompilescope: "all"rebuild the dependency graph and queue every project .dsm / .dsf
recompilescope: "file", sourceFilequeue one file into the debounce queue
cleanGeneratedShadersdelete the generated *.ush includes, then queue a full rescan
previewMaterialsourceFilerender one preview synchronously and write preview.json

A request file is deleted unconditionally — after a successful dispatch, after a read failure, after a JSON parse failure, and after an unrecognized action. There is no reply file, no error file and no log line for a malformed request: it simply vanishes. And because the poller may open a file that is still being written, write the JSON to a temporary name elsewhere and rename it into Requests/ so it appears atomically.

Compilation through this path is always in-memory; a queued file compiles after the debounce window (Save Debounce Seconds, clamped to [0.05, 10.0], default 0.25) plus up to 0.1 s of poll delay, and only if it still exists on disk.

Diagnostics the extension reads

diagnostics.json has the shape { "version": 1, "updatedAtUtc": "…", "files": [ { "path": "…", "diagnostics": [ … ] } ] }. Optional fields are omitted entirely when empty.

FieldPresenceValue
messagealwaysthe diagnostic text
detailwhen non-emptythe raw underlying line
stagewhen non-emptygenerate, materialCompile or virtualFunctionSync
assetPathwhen non-emptyobject path of the asset involved
shaderPlatform, qualityLevelwhen non-emptymaterial-compile diagnostics only
codewhen non-emptygenerate-error, material-compile or virtual-function-sync
line, columnalways1-based, defaulting to 1
severityalwayserror
sourcealwaysDreamShader, DreamShader Generate, DreamShader Material Compile or DreamShader VirtualFunction

severity is always the literal error. The plugin never emits a warning, information or hint through this file — parse warnings are appended to compile messages instead. A client that filters on severity should treat a missing or unknown value as an error.

Locations are recovered from messages shaped <path>(<line>,<column>): <message>; a line with no parseable location is reported at 1,1. Material-compile diagnostics carry a display message of the form [{ShaderPlatform} / {QualityLevel}] {Message}. Since UE 5.7 shaderPlatform carries a shader-format name such as PCD3D_SM6; below 5.7 it carries a feature-level name such as SM6.

Streaming preview

The preview panel connects to ws://127.0.0.1:17864 and drives the plugin's renderer.

MessageDirectionPurpose
previewMaterialclient → editorstart a preview session for a .dsm
previewControlclient → editoradjust the active session
previewResulteditor → clientsession start result, or a mid-stream error
previewFrameeditor → clientmetadata for the PNG that follows

Every outbound message is a WebSocket binary frame whose payload is a 4-byte little-endian length, a 1-byte type tag (1 = UTF-8 JSON, 2 = raw PNG), then the payload. A JSON message is always sent first and the matching binary message immediately after, on the same connection; the client pairs them by arrival order. Inbound messages are plain UTF-8 JSON with no prefix and no tag.

Streaming is both rate-limited and acknowledgement-gated: a new frame starts only when the client has acknowledged the previous one and the frame interval has elapsed.

frameRate does not mean "keep the current rate" when omitted from a previewControl message — the reader initializes it to 2.0 before looking for the field, so a control message sent purely to acknowledge a frame or nudge the camera silently drops the session to 2 FPS. Orbit angles behave the opposite way and are preserved. Send the current frameRate on every previewControl.

A session example, client side:

→ {"type":"previewMaterial","sourceFile":"…/M_Sample.dsm","mesh":"shaderball",
   "width":512,"height":512,"requestId":"8f3c1b","stream":true,"frameRate":12}
← [len][1] {"type":"previewResult","requestId":"8f3c1b","status":"ready", … "imagePath":"…"}
← [len][2] <PNG bytes>
← [len][1] {"type":"previewFrame","requestId":"8f3c1b","frameIndex":0, …}
← [len][2] <PNG bytes>
→ {"type":"previewControl","requestId":"8f3c1b","ackFrameIndex":0,"frameRate":12,"orbitYaw":-140.0}
← [len][1] {"type":"previewFrame","requestId":"8f3c1b","frameIndex":1, …}
← [len][2] <PNG bytes>

The renderer's own limits — .dsm only, the size clamp, the mesh fallback, the missing pitch clamp — are on Editor Tools.

VSCode commands

Names are the extension's, not the plugin's, and are surfaced under the DreamShaderLang group.

DreamShaderLang: Recompile Current Source
DreamShaderLang: Recompile All Sources
DreamShaderLang: Clean Generated Shaders
DreamShaderLang: Show Bridge Panel
DreamShaderLang: Refresh Bridge Diagnostics
DreamShaderLang: Show Material Preview
DreamShaderLang: Install Package from GitHub
DreamShaderLang: Browse Package Store
DreamShaderLang: Update Installed Packages
DreamShaderLang: Remove Installed Package
DreamShaderLang: Open Packages Folder
DreamShaderLang: Add Package Store Index Source
DreamShaderLang: Remove Package Store Index Source
DreamShaderLang: Create Package Step by Step
DreamShaderLang: Create DreamShader Material
DreamShaderLang: Create DreamShader Function File
DreamShaderLang: Create DreamShader Header
DreamShaderLang: Create DreamShader Texture Sample
DreamShaderLang: Create DreamShader Noise Material

VSCode settings

Extension-declared, so the extension repository is authoritative. Keep the workspace pointed at the Unreal project root — or set dreamshader.projectRoot — so the extension can resolve DShader, DShader/Packages and Saved/DreamShader/Bridge.

SettingDefaultUse
dreamshader.projectRoot(auto)Unreal project root, when the workspace is not opened there
dreamshader.previewWebSocketPort17864the plugin's preview WebSocket port — it is fixed on the plugin side
dreamshader.previewAutoRefreshDelayMs1200delay before saving and refreshing after an edit
dreamshader.previewTransportwebsocketuse the WebSocket, or force file bridge requests
dreamshader.previewLiveFrameRate2maximum streamed FPS; 0 disables continuous frames
dreamshader.packageStoreIndexUrls(default index)one or more package store index JSON URLs
dreamshader.enableGitHubPackageSearchtruealso search GitHub for the dreamshader-package topic
{
  "dreamshader.packageStoreIndexUrls": [
    "https://raw.githubusercontent.com/TypeDreamMoon/dreamshader-package-index/main/packages.json"
  ],
  "dreamshader.enableGitHubPackageSearch": true
}

Notes

  • The plugin's previewWebSocketPort counterpart is not configurable: the server always binds 127.0.0.1:17864, and a connection from any other address is refused. Two editors on one machine cannot both serve previews — the second logs a listen warning and runs without streaming.
  • Diagnostics reported for an imported header map back to the file you actually edited: import lines are replaced by blank lines and each inlined file is bracketed with source markers, so line and column stay put.
  • The Material Content Browser's Gen page shows only the first diagnostic per file. The extension shows all of them. See Editor Tools.

Example

Running Tools ▸ DreamShader ▸ Open Dream Shader Workspace (VSCode) on a default project touches:

<Project>/DShader/DreamShader.code-workspace                    rewritten
<Project>/Saved/DreamShader/Bridge/material-expressions.json    rewritten
<Project>/Saved/DreamShader/Bridge/settings.json                rewritten
<Project>/Saved/DreamShader/Bridge/substrate-builtins.json      rewritten
<Project>/Saved/DreamShader/Bridge/bridge.db                    tables replaced

and then launches, for a code.cmd candidate with Open In New Window at its default:

%ComSpec% /C ""C:/Users/<user>/AppData/Local/Programs/Microsoft VS Code/bin/code.cmd"  "C:/Projects/MyGame/DShader/DreamShader.code-workspace""

Asking a running editor to recompile one file, without VSCode:

$req = @{ action = "recompile"; scope = "file"; sourceFile = "C:/Projects/MyGame/DShader/Materials/M_Sample.dsm" }
$dir = "C:\Projects\MyGame\Saved\DreamShader\Bridge\Requests"
$tmp = Join-Path $env:TEMP ("ds-" + [guid]::NewGuid() + ".json")
$req | ConvertTo-Json | Set-Content -Path $tmp -Encoding utf8
Move-Item $tmp (Join-Path $dir ([IO.Path]::GetFileName($tmp)))

Where next

On this page