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.
| Side | Owns |
|---|---|
| Unreal plugin | compilation, diagnostics, the bridge artifacts, the preview renderer, DreamShader.code-workspace |
| Editor extension | syntax highlighting, completion, hovers, navigation, the preview panel and its camera, package install/update, the package manifest and lock file |
Which side implements what
| Surface | Implemented by | Notes |
|---|---|---|
| Menus, toolbar, context menus, the browser tab | plugin | Editor Tools |
| Auto-compile-on-save, the diagnostics store | plugin | writes three diagnostic sinks |
| Preview rendering (the PNG frames) | plugin | the renderer, the mesh set and the clamps |
| Preview camera control, pitch clamping, frame acknowledgement | extension | the plugin applies no pitch clamp of its own |
| Decompiler | plugin | Decompiler |
.dsm / .dsh / .dsf highlighting, completion, hovers | extension | fed by the manifests the plugin exports |
DreamShader.code-workspace | plugin | rewritten on every Open Dream Shader Workspace |
dreamshader.package.json, dreamshader.lock.json, install/update commands | extension | no plugin C++ reads either file — Packages |
DShader/Packages creation, import resolution, auto-compile exclusion | plugin | Packages |
The extensions
Neither extension ships inside the plugin. Install one separately.
| Editor | Repository | Provides |
|---|---|---|
| VSCode | TypeDreamMoon/dreamshader-language-support | Syntax 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 Rider | tsdaer/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 |
| Capability | VSCode | Rider |
|---|---|---|
| File type recognition | yes | yes |
| Syntax highlighting | yes | yes |
| Parser model | parser-based language service | JetBrains PSI parser |
| Completion, hover, signature help | yes | yes |
| Go to definition, find references | yes | yes |
| Local diagnostics | yes | yes |
| Unreal bridge diagnostics | yes | yes |
| Inlay hints / semantic tokens | yes | yes |
| Material preview panel | yes | plugin-dependent |
| Package commands | yes | plugin-dependent |
| Authoring templates | yes | plugin-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
| Aspect | Value |
|---|---|
| Path | <SourceDirectory>/DreamShader.code-workspace — <Project>/DShader/ by default |
| Encoding | UTF-8 without BOM, pretty-printed by Unreal's JSON writer (tab indentation) |
| Platform | Windows 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.
| Key | Value | Purpose |
|---|---|---|
folders[0].name | DreamShader Source | display name of the single workspace folder |
folders[0].path | . | the folder holding the workspace file — <SourceDirectory> itself |
settings["files.associations"]["*.dsm"] | dreamshaderlang | language id for material sources |
settings["files.associations"]["*.dsh"] | dreamshaderlang | language id for headers |
settings["files.associations"]["*.dsf"] | dreamshaderlang | language 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
| Step | Action | On failure |
|---|---|---|
| 1 | Re-export material-expressions.json | logged, the command continues |
| 2 | Re-export settings.json | logged, the command continues |
| 3 | Re-export substrate-builtins.json | logged, the command continues |
| 4 | Write DreamShader.code-workspace | toast + warning, the command aborts |
| 5 | Launch an editor on the workspace file | toast + 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.
| Order | Mechanism | Detail |
|---|---|---|
| 1 | VSCode | the 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 |
| 2 | Shell default application | LaunchFileInDefaultExternalApplication with the Edit verb — whatever is registered for .code-workspace |
| 3 | Notepad | %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.
| Order | Candidate |
|---|---|
| 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 |
| 9 | for 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
| Aspect | Value |
|---|---|
| Setting | Open In New Window — bOpenInNewWindow, category Editor |
| Default | true |
| Effect | when 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.
| Action | Launcher | Window behaviour |
|---|---|---|
| Open Dream Shader Workspace (VSCode), menu and toolbar | workspace launcher | honours bOpenInNewWindow |
| Open source (Material Content Browser, Gen page) | file launcher | always --reuse-window |
| OpenVirtualFunction (asset context menu) | file launcher | always --reuse-window, positioned at the declaration's line and column |
| Export DSM / Export DSF post-export open | preferred-editor chain | always --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}.
| Toast | Log | Cause |
|---|---|---|
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 text | a 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 text | Notepad launched |
DreamShader could not open workspace: {Path} | Warning — Failed to open DreamShader workspace: {Path} | every mechanism failed; the file was still written |
| Writer error | Cause |
|---|---|
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.
| Artifact | Direction | Contents |
|---|---|---|
Requests/*.json | extension → editor | recompile, clean and one-shot preview commands |
diagnostics.json | editor → extension | all current diagnostics, grouped by source file |
diagnostics/index.json + diagnostics/<md5>.json | editor → extension | the same data sharded per file, for incremental reads |
bridge.db | editor → extension | SQLite mirror of the diagnostics and the three manifests |
material-expressions.json | editor → extension | reflected UMaterialExpression catalogue for UE.Expression completion since 1.2.10 |
settings.json | editor → extension | ShadingModel / BlendMode / MaterialDomain alias tables |
substrate-builtins.json | editor → extension | Substrate.* catalogue with snippets; supported: false below UE 5.4 |
preview.json + Preview/*.png | editor → extension | result manifest and image for a one-shot preview |
ws://127.0.0.1:17864 | bidirectional | streaming 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
| Aspect | Value |
|---|---|
| Directory | <Project>/Saved/DreamShader/Bridge/Requests/ |
| Discovery | *.json, files only, non-recursive |
| Poll interval | 0.1 s |
| Consumption | every 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:
action | Required fields | Effect |
|---|---|---|
recompile | scope: "all" | rebuild the dependency graph and queue every project .dsm / .dsf |
recompile | scope: "file", sourceFile | queue one file into the debounce queue |
cleanGeneratedShaders | — | delete the generated *.ush includes, then queue a full rescan |
previewMaterial | sourceFile | render 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.
| Field | Presence | Value |
|---|---|---|
message | always | the diagnostic text |
detail | when non-empty | the raw underlying line |
stage | when non-empty | generate, materialCompile or virtualFunctionSync |
assetPath | when non-empty | object path of the asset involved |
shaderPlatform, qualityLevel | when non-empty | material-compile diagnostics only |
code | when non-empty | generate-error, material-compile or virtual-function-sync |
line, column | always | 1-based, defaulting to 1 |
severity | always | error |
source | always | DreamShader, 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.
| Message | Direction | Purpose |
|---|---|---|
previewMaterial | client → editor | start a preview session for a .dsm |
previewControl | client → editor | adjust the active session |
previewResult | editor → client | session start result, or a mid-stream error |
previewFrame | editor → client | metadata 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 MaterialVSCode 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.
| Setting | Default | Use |
|---|---|---|
dreamshader.projectRoot | (auto) | Unreal project root, when the workspace is not opened there |
dreamshader.previewWebSocketPort | 17864 | the plugin's preview WebSocket port — it is fixed on the plugin side |
dreamshader.previewAutoRefreshDelayMs | 1200 | delay before saving and refreshing after an edit |
dreamshader.previewTransport | websocket | use the WebSocket, or force file bridge requests |
dreamshader.previewLiveFrameRate | 2 | maximum streamed FPS; 0 disables continuous frames |
dreamshader.packageStoreIndexUrls | (default index) | one or more package store index JSON URLs |
dreamshader.enableGitHubPackageSearch | true | also 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
previewWebSocketPortcounterpart is not configurable: the server always binds127.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 replacedand 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
Editor Tools
The menu entry that writes the workspace, and the bridge artifacts.
Packages
The half of the package system the extension owns.
Commandlet
Compiling with no editor and no bridge at all.
Installation
Installing the plugin and one of the extensions.
Diagnostics
Every message the bridge can publish.
Decompiler
Exporting an existing UMaterial or UMaterialFunction back to .dsm / .dsf — what round-trips, what falls back to UE.Expression, and what is simply lost.
Commandlet
-run=DreamShader — every command and switch, path resolution, exit codes, and why it writes real assets when the editor does not.