DreamShaderLang
工具链

VSCode 与 Rider

语言扩展、生成的 DreamShader.code-workspace,以及两侧交换的 bridge 产物。

DreamShaderLang 是两个相互独立的产品:Unreal 插件,和你编辑器里的语言扩展。它们从不互相直接调用 —— 交换发生在 <Project>/Saved/DreamShader/Bridge/ 下的文件和一条回环 WebSocket 上。 出问题时,先分清哪边负责什么能省很多时间。

一侧负责
Unreal 插件编译、诊断、bridge 产物、预览渲染器、DreamShader.code-workspace
编辑器扩展语法高亮、补全、Hover、导航、预览面板及其相机、package 安装/更新、package manifest 和锁文件

哪边实现了什么

实现方说明
菜单、工具栏、右键菜单、浏览器面板插件编辑器工具
保存即编译、诊断存储插件写三个诊断 sink
预览渲染(PNG 帧)插件渲染器、网格集合和各种钳制
预览相机控制、pitch 钳制、帧确认扩展插件自己不做任何 pitch 钳制
反编译器插件反编译导出
.dsm / .dsh / .dsf 高亮、补全、Hover扩展数据来自插件导出的 manifest
DreamShader.code-workspace插件每次 Open Dream Shader Workspace 都重写
dreamshader.package.jsondreamshader.lock.json、安装/更新命令扩展插件里没有任何 C++ 读这两个文件 —— Package
DShader/Packages 创建、import 解析、自动编译排除插件Package

两个扩展

两个扩展都不随插件一起发布,需要单独安装。

编辑器仓库提供
VSCodeTypeDreamMoon/dreamshader-language-support语法高亮、snippets、补全、Go to Definition、Find References、Hover、Signature Help、本地诊断、Unreal 桥接诊断、Package 命令、快速模板、材质预览面板
JetBrains Ridertsdaer/dreamshader-language-support.dsm / .dsf / .dsh 文件类型、语法与 PSI 解析、高亮、补全、导航、诊断、Unreal Bridge 集成、语义 token、inlay hints、Package 工具
能力VSCodeRider
文件类型识别
语法高亮
解析模型parser-based 语言服务JetBrains PSI 解析器
补全、Hover、Signature Help
跳转定义、查找引用
本地诊断
Unreal 桥接诊断
Inlay hints / 语义 token
材质预览面板取决于插件
Package 命令取决于插件
创作模板取决于插件

发布流程会把最新的 VSCode 扩展产物附到每个插件 GitHub release 上,所以两边版本大致同步。 扩展侧的设置 —— 项目根目录覆盖、预览帧率、package store index URL —— 由扩展声明, 不属于 UDreamShaderSettings,以扩展自己的仓库为准。

生成的 workspace

Tools ▸ DreamShader ▸ Open Dream Shader Workspace (VSCode) —— 以及同名的工具栏按钮 —— 会写一个文件并用编辑器打开它。since 1.2.1

项目
路径<SourceDirectory>/DreamShader.code-workspace —— 默认 <Project>/DShader/
编码UTF-8 无 BOM,由 Unreal 的 JSON writer 美化输出(tab 缩进)
平台仅 Windows —— 探测用的是 Windows 环境变量、; 分隔的 PATHcmd.exenotepad.exe

源目录不存在时会被创建。文件的全部内容:

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

writer 会写出的所有键。没有别的键,也没有任何条件分支。

用途
folders[0].nameDreamShader Source唯一那个 workspace 文件夹的显示名
folders[0].path.存放 workspace 文件的目录 —— 也就是 <SourceDirectory> 本身
settings["files.associations"]["*.dsm"]dreamshaderlang材质源文件的 language id
settings["files.associations"]["*.dsh"]dreamshaderlang头文件的 language id
settings["files.associations"]["*.dsf"]dreamshaderlang函数源文件的 language id since 1.3.5

这个文件每次调用都被整体重写。 writer 序列化的是一个固定对象;它从不读取、合并或保留原有内容。 你手工加的任何 launchtasksextensions 或额外 settings 条目,都会在下次执行该命令时被摧毁。 把个人配置放在另一个 .code-workspace 文件里,或者放进 <SourceDirectory>/.vscode/settings.json —— 这两处这条命令都不碰。

这条命令的执行顺序

步骤动作失败时
1重新导出 material-expressions.json记日志,命令继续
2重新导出 settings.json记日志,命令继续
3重新导出 substrate-builtins.json记日志,命令继续
4DreamShader.code-workspacetoast + warning,命令中止
5用编辑器打开该 workspace 文件toast + warning

第 1–3 步重写的就是 bridge 在编辑器启动时写的那三份 manifest,所以刚装好的扩展不用重启编辑器就能拿到当前数据。 见 Bridge 产物

启动回退链

第一个成功的机制胜出,其余不再尝试。

顺序机制细节
1VSCode第一个能拿到有效进程句柄的已发现可执行文件。.cmd / .bat 候选经由 %ComSpec%(回退到 C:/Windows/System32/cmd.exe)带 /C 隐藏运行;.exe 候选直接启动
2Shell 默认应用LaunchFileInDefaultExternalApplicationEdit 动词 —— 也就是系统为 .code-workspace 注册的那个程序
3Notepad存在时用 %SystemRoot%\System32\notepad.exe,否则用裸 notepad.exe
(都没有)失败 toast,以及日志里的一条 warning

VSCode 可执行文件探测

按下列精确顺序探测。只保留确实作为文件存在的路径,并去重。

顺序候选
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对每个 ; 分隔的 PATH 条目,按 PATH 顺序:code.cmdcode.exeCode.execode-insiders.cmdCode - Insiders.exe

没有任何设置可以指定 VSCode 可执行文件。 非标准安装位置只能通过把它放进 PATH 来触达。

各操作用的是哪个 launcher

项目
设置项Open In New Window —— bOpenInNewWindow,分类 Editor
默认true
效果false 时,VSCode 命令行后面追加 --reuse-window。为 true 时不传任何标志,由 VSCode 自己决定

bOpenInNewWindow 只被 workspace launcher 读取。其他每一个用 VSCode 打开文件的 DreamShader 操作 都走另一个 launcher,那个 launcher 无视该设置,总是传 --reuse-window -g "<path>:<line>:<column>"

操作Launcher窗口行为
Open Dream Shader Workspace (VSCode),菜单和工具栏workspace launcher遵循 bOpenInNewWindow
Open source(Material Content Browser 的 Gen 页)file launcher总是 --reuse-window
OpenVirtualFunction(资产右键菜单)file launcher总是 --reuse-window,并定位到声明的行列
Export DSM / Export DSF 导出后的打开首选编辑器链用 VSCode 时总是 --reuse-window

file launcher 会把行列钳到至少 1,它自己的回退链是 VSCode → Shell 默认应用(Edit 动词)→ Notepad, 和 workspace 链形状一致。

诊断

toast 文本和日志文本不同,两者都列出。运行期替换写作 {Placeholder}

Toast日志原因
DreamShader failed to create workspace: {Error}Warning —— Failed to create DreamShader workspace: {Error}workspace 文件写不了;{Error} 是下面三个 writer 错误之一
Opened DreamShader workspace in VSCode: {Path}Display —— 同文本某个 VSCode 候选启动了
Opened DreamShader workspace: {Path}Display —— Opened DreamShader workspace with the default editor: {Path}Shell 默认应用启动了
Opened DreamShader workspace in Notepad: {Path}Display —— 同文本Notepad 启动了
DreamShader could not open workspace: {Path}Warning —— Failed to open DreamShader workspace: {Path}所有机制都失败;文件仍然写出来了
Writer 错误原因
DreamShader source directory is empty.解析出的源目录归一化后是空串
Failed to create DreamShader source directory '{Path}'.源目录不存在且创建失败
Failed to write DreamShader workspace file '{Path}'.文件保存失败 —— 只读、被占用、磁盘满

扩展消费什么

扩展读写的每个产物都在 <Project>/Saved/DreamShader/Bridge/ 下,加上那个回环 WebSocket 端点。

产物方向内容
Requests/*.json扩展 → 编辑器重编译、清理和单次预览命令
diagnostics.json编辑器 → 扩展当前全部诊断,按源文件分组
diagnostics/index.json + diagnostics/<md5>.json编辑器 → 扩展同一份数据按文件分片,便于增量读取
bridge.db编辑器 → 扩展诊断和三份 manifest 的 SQLite 镜像
material-expressions.json编辑器 → 扩展反射得到的 UMaterialExpression 目录,用于 UE.Expression 补全 since 1.2.10
settings.json编辑器 → 扩展ShadingModel / BlendMode / MaterialDomain 别名表
substrate-builtins.json编辑器 → 扩展带 snippet 的 Substrate.* 目录;UE 5.4 以下 supported: false
preview.json + Preview/*.png编辑器 → 扩展单次预览的结果清单和图片
ws://127.0.0.1:17864双向带轨道控制的流式预览

bridge.db 从插件这一侧看是只写的,而且不是持久状态:bridge 启动时关闭时都会删掉它, 每个写入者都在事务里整表替换。插件里没有任何东西读回过一行。把它当作 JSON 产物的一个便于查询的镜像, 只在编辑器运行期间有效 —— 绝不要拿它存客户端状态。

请求文件

项目
目录<Project>/Saved/DreamShader/Bridge/Requests/
发现方式*.json,只看文件,不递归
轮询间隔0.1 秒
消费方式每个发现的文件都在该轮循环结束时被删除

文件名无关紧要,只有 JSON 内容有意义。actionscope 都做大小写不敏感匹配。一共只有四个 action:

action必需字段效果
recompilescope: "all"重建依赖图并把每个项目 .dsm / .dsf 排入队列
recompilescope: "file"sourceFile把一个文件排进防抖队列
cleanGeneratedShaders删掉生成的 *.ush include,然后排入一次全量扫描
previewMaterialsourceFile同步渲染一次预览并写 preview.json

请求文件是无条件删除的 —— 派发成功后删、读失败后删、JSON 解析失败后删、action 无法识别也删。 畸形请求没有回复文件、没有错误文件、也没有日志行:它就是消失了。而且轮询器可能打开一个还在写入中的文件, 所以请把 JSON 写到别处的临时名字上,再重命名Requests/,让它原子地出现。

走这条路径的编译永远是内存内的;排队的文件在防抖窗口 (Save Debounce Seconds,钳制到 [0.05, 10.0],默认 0.25)加最多 0.1 秒 轮询延迟之后编译, 且仅当它还存在于磁盘上。

扩展读到的诊断

diagnostics.json 的形状是 { "version": 1, "updatedAtUtc": "…", "files": [ { "path": "…", "diagnostics": [ … ] } ] }。 可选字段为空时整条省略。

字段出现时机
message始终诊断文本
detail非空时底层原始行
stage非空时generatematerialCompilevirtualFunctionSync
assetPath非空时相关资产的 object path
shaderPlatformqualityLevel非空时仅材质编译诊断
code非空时generate-errormaterial-compilevirtual-function-sync
linecolumn始终从 1 开始,默认 1
severity始终error
source始终DreamShaderDreamShader GenerateDreamShader Material CompileDreamShader VirtualFunction

severity 恒为字面量 error。插件从不通过这个文件发出 warning、information 或 hint —— 解析警告是被追加到编译消息里的。按严重度过滤的客户端应该把缺失或未知的值当作 error。

位置是从形如 <path>(<line>,<column>): <message> 的消息里还原的;没有可解析位置的行报在 1,1。 材质编译诊断带一条形如 [{ShaderPlatform} / {QualityLevel}] {Message} 的展示消息。 UE 5.7 起 shaderPlatform 带的是 shader format 名,例如 PCD3D_SM6;5.7 以下带的是 feature level 名,例如 SM6

流式预览

预览面板连接 ws://127.0.0.1:17864,驱动插件的渲染器。

消息方向用途
previewMaterial客户端 → 编辑器为一个 .dsm 开启预览会话
previewControl客户端 → 编辑器调整当前会话
previewResult编辑器 → 客户端会话启动结果,或流中途的错误
previewFrame编辑器 → 客户端紧随其后那张 PNG 的元数据

每条出站消息都是 WebSocket binary 帧,载荷是 4 字节小端长度、1 字节类型标签 (1 = UTF-8 JSON,2 = 原始 PNG),然后是载荷本体。JSON 消息总是先发,匹配的二进制消息紧接着在同一连接上发, 客户端按到达顺序配对。入站消息是纯 UTF-8 JSON,没有长度前缀也没有标签字节。

流式既限速又受确认门控:只有客户端已确认上一帧帧间隔已过,新帧才会开始。

previewControl 里省略 frameRate 并不等于"保持当前帧率" —— 读取器在找这个字段之前就把它初始化成了 2.0,所以一条纯粹用来确认帧或微调相机的控制消息会静默把会话掉到 2 FPS。轨道角度的行为正好相反,会被保留。 每一条 previewControl 都带上当前的 frameRate

一次会话,客户端视角:

→ {"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>

渲染器自身的限制 —— 只支持 .dsm、尺寸钳制、网格回退、没有 pitch 钳制 —— 见编辑器工具

VSCode 命令

这些名字属于扩展,不属于插件,都在 DreamShaderLang 命令组下。

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 设置

这些由扩展声明,以扩展仓库为准。把 workspace 指向 Unreal 项目根目录 —— 或者设置 dreamshader.projectRoot —— 这样扩展才能解析 DShaderDShader/PackagesSaved/DreamShader/Bridge

设置默认值用途
dreamshader.projectRoot(自动)workspace 不是在项目根目录打开时,指定 Unreal 项目根
dreamshader.previewWebSocketPort17864插件的预览 WebSocket 端口 —— 插件侧是固定的
dreamshader.previewAutoRefreshDelayMs1200编辑后保存并刷新预览前的延迟
dreamshader.previewTransportwebsocket用 WebSocket,或强制走 file 文件桥请求
dreamshader.previewLiveFrameRate2流式预览帧率上限;0 关闭连续帧
dreamshader.packageStoreIndexUrls(默认 index)一个或多个 package store index JSON 地址
dreamshader.enableGitHubPackageSearchtrue同时按 GitHub topic dreamshader-package 搜索
{
  "dreamshader.packageStoreIndexUrls": [
    "https://raw.githubusercontent.com/TypeDreamMoon/dreamshader-package-index/main/packages.json"
  ],
  "dreamshader.enableGitHubPackageSearch": true
}

说明

  • 插件这一侧对应的端口不可配置:服务器总是绑定 127.0.0.1:17864,来自其他地址的连接会被拒绝。 同一台机器上的两个编辑器无法同时提供预览 —— 第二个会记一条 listen 警告,然后在没有流式预览的情况下继续跑。
  • 报在被 import 的头文件上的诊断会映射回你实际编辑的那个文件:import 行被替换成空行, 每个被内联的文件用来源标记括起来,所以行列位置不会漂。
  • Material Content Browser 的 Gen 页每个文件只显示第一条诊断,扩展则显示全部。 见编辑器工具

示例

在一个默认项目上执行 Tools ▸ DreamShader ▸ Open Dream Shader Workspace (VSCode) 会动到:

<Project>/DShader/DreamShader.code-workspace                    重写
<Project>/Saved/DreamShader/Bridge/material-expressions.json    重写
<Project>/Saved/DreamShader/Bridge/settings.json                重写
<Project>/Saved/DreamShader/Bridge/substrate-builtins.json      重写
<Project>/Saved/DreamShader/Bridge/bridge.db                    整表替换

然后,对一个 code.cmd 候选、Open In New Window 保持默认时,启动的是:

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

不通过 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)))

继续阅读

本页目录