dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DSH plugin: render code-driven music videos (music-code-mv) — deterministic Canvas2D/Three.js shots, headless-Chrome or browserless skia engine, ffmpeg AV1/HEVC up to 4K/120fps/12-bit
Render music videos by writing code. / 把一支 MV 写成代码。
A DeepSeek Harness plugin that turns a storyboard into a deterministic, frame-accurate video. No video-generation model, no stock footage, no timeline editor — every frame is drawn by Canvas2D or Three.js code, captured in headless Chrome, and encoded with ffmpeg. 一个 DeepSeek Harness 插件:把分镜表变成逐帧确定的视频。不用视频生成模型、不用素材库、不用时间线编辑器——每一帧都由 Canvas2D 或 Three.js 代码画出,无头 Chrome 逐帧截取,ffmpeg 合成。
dsh plugin --profile demo add dsh-music-code-mv
dsh --profile demo
仓库已打上
dsh-plugin话题(GitHub 话题页会收录本插件);收录进 awesome-deepseek-agent 的条目见 docs/awesome-deepseek-agent-entry.md。 The repo carries thedsh-plugintopic, and a ready entry for awesome-deepseek-agent lives in docs/awesome-deepseek-agent-entry.md.
中文优先 / Chinese-first. 工具卡片与返回文本以中文为主、英文并列;浏览器端的操作面是对话视图环里的「MV 工坊」原生标签页——首屏就是「一键进入子代理」,状态、清晰度/风格/帧率预设、联系表直渲与风格画廊全在其中,不内嵌任何网页;右侧边栏另有一块只读的原生状态镜像(无 iframe),也仍可直接用浏览器打开工作室地址。 Tool cards and returned text lead with Chinese; the browser half ships a Chinese MV Studio page in the DSH right sidebar, also reachable directly in a browser.
| video model / 视频生成模型 | music-code-mv | |
|---|---|---|
| reproducibility / 可复现 | none, every run differs / 没有,每次都不一样 | every frame is a pure function of t / 每帧都是 t 的纯函数 |
| iteration / 迭代 | re-generate the whole clip / 整段重抽 | frame cache: only changed frames re-render / 帧缓存:只重渲变过的帧 |
| typography / 排版 | unreliable, text gets mangled / 不可靠,文字会糊 | text is text / 文字就是文字 |
| resolution / 分辨率 | model-limited / 受模型限制 | any size, up to 4K60, plus HDR10 / 任意尺寸,最高 4K60,另支持 HDR10 |
| cost / 成本 | per-generation billing / 按次计费 | your own CPU/GPU / 用你自己的 CPU/GPU |
The core constraint that buys all of this: renderAt(t) is a pure function. Same t, same pixels, in any order.
换来这一切的核心约束:renderAt(t) 是纯函数。同一个 t 就是同样的像素,与调用顺序无关。
Rendering / 渲染
requestAnimationFrame, no wall clock / 虚拟时间确定性渲染——不用 requestAnimationFrame,不读墙钟preserveDrawingBuffer handled for you / 可选 Three.js 镜头(import map 接入),preserveDrawingBuffer 已替你处理Performance / 性能 (all measured on an Intel Core Ultra 5 125H + Arc iGPU / 全部实测于 Intel Core Ultra 5 125H + Arc 核显)
zscale), not just re-tagging / HDR10 输出:10-bit HEVC、Rec.2020、PQ——走真实的 SDR→PQ 变换(zscale),不是只打标签--encoder=libx265 gives a compliant master). HDR Vivid (CUVA TU-068 dynamic metadata) is not supported: the stock ffmpeg build here ships no CUVA encoder — an external CUVA toolchain would be required — so no doc or video copy claims it. / HDR10 ✓ / HDR Vivid ✗——HDR10 已交付:10-bit HEVC、Rec.2020、PQ 与静态母版元数据(QSV 无法嵌入母版元数据,渲染时会告警,合规母版请用 --encoder=libx265)。HDR Vivid(CUVA TU-068 动态元数据)不支持:本机 stock ffmpeg 没有 CUVA 编码器,需外部 CUVA 工具链,因此任何文档与视频文案都不声称支持。图形界面 / GUI (0.2.0)
+ 里选 / a right-sidebar MV Studio page, auto-opened once, re-openable from the tab strip's +conversation.view,仅激活时挂载),首屏是「一键进入子代理」主色大按钮 + 当前预设回显,没有标题栏小按钮、没有右下角悬浮窗 / a native tab in the conversation view ring (peer of 对话/轨迹, mounted only while active) whose first screen is the primary one-click subagent button plus the current-preset readout — no title-bar chip, no floating dockfile:// 环境下自动退化为「在浏览器打开」提示 / Chinese copy, hand-written browser bundle with no build step, graceful fallback under file://Authoring / 创作
Math.random() in draw code) and hash1/hash2 helpers / 种子化 PRNG(绘制代码里没有 Math.random())与 hash1/hash2 工具Node.js ≥ 18 · Google Chrome or Edge · ffmpeg
npm install three puppeteer-core ffmpeg-static
⚠️ Recent npm versions block lifecycle scripts by default and will silently skip the
ffmpeg-staticbinary download. Either runnpm approve-scripts ffmpeg-static, or install@ffmpeg-installer/ffmpeg, which ships a prebuilt binary and needs no install script. 新版 npm 默认拦截生命周期脚本,会静默跳过ffmpeg-static的二进制下载。用npm approve-scripts ffmpeg-static,或改装自带二进制的@ffmpeg-installer/ffmpeg。
git clone https://github.com/ayst-z/music-code-mv
cd music-code-mv
dsh plugin add .
dsh --profile demo --dump-config | grep dsh-music-code-mv # should show a "# == dsh-music-code-mv" layer
# Windows / Windows 下等价写法:
# dsh --profile demo --dump-config | findstr /C:"dsh-music-code-mv"
dsh --profile demo
This package ships plain JavaScript, so there is no build step and no allowBuilds authorization prompt.
本包发布的是纯 JavaScript,没有构建步骤,也不会触发构建授权提示。
Other routes / 其他方式:
dsh plugin --profile demo add github:ayst-z/music-code-mv#<sha> # from git, pinned / 从 git,锁 commit
pnpm pack && dsh plugin --profile demo add ./dsh-music-code-mv-0.2.0.tgz # from a tarball / 从 tarball
dsh plugin --profile demo remove dsh-music-code-mv # uninstall / 卸载
The plugin registers seven tools on ctx.tools. / 插件在 ctx.tools 上注册七个工具。
music_mv_guideRead the bundled authoring guide. Call this before directing a video. / 读取内置创作指南。指导一支视频之前先调它。
| param | type | required | description |
|---|---|---|---|
topic | string | no | skill (default) · styles · techniques · threejs · environment · lineage / 主题,默认 skill |
music_mv_probeCheck the machine: Node, Chrome/Edge, ffmpeg, puppeteer-core, three.js, and a live WebGL context. Prints READY or names exactly what is missing. / 检查本机环境:Node、Chrome/Edge、ffmpeg、puppeteer-core、three.js 与真实 WebGL 上下文。打印 READY,或明确列出缺什么。
| param | type | required | description |
|---|---|---|---|
projectDir | string | no | directory to probe for a local node_modules / 要探测 node_modules 的目录,默认当前目录 |
music_mv_initScaffold a project: timeline, seeded PRNG, Canvas2D stage, lyric parsing, and a working 21-second six-shot demo. / 生成工程骨架:时间线、种子化 PRNG、Canvas2D 舞台、歌词解析,以及可直接跑的 21 秒六镜头 demo。
| param | type | required | description |
|---|---|---|---|
dir | string | yes | project directory to create / 要创建的工程目录 |
force | boolean | no | overwrite a non-empty directory / 覆盖非空目录 |
music_mv_renderRender a contact sheet, stills, or a full MP4. Frames are cached, so a re-run resumes instead of starting over. / 渲染联系表、静帧或完整 MP4。帧有缓存,重跑是续渲而不是从头开始。
NumPy 引擎的 hi-res / HDR 通道(render-np.py,与上表同契约):
| 能力 | 命令 | 说明 |
|---|---|---|
| 流式输出 | --stream | 帧直管道给 ffmpeg,frames/ 零字节(4K120 一次渲省 17–18 GB);代价是不能断点续渲 |
| hi-res | --w=3840 --h=2160 --fps=120 --format=jpeg | CLI 覆盖工程参数;frames/.renderpass 档位指纹防止复用错档缓存;jpeg 比 png 快约 3 倍 |
| HDR10 | --depth=10 --hdr10 | 强制走流式:v*257 → rgb48le → yuv420p10le + BT.2020/PQ + libx265 |
| HDR Vivid | --hdr-vivid | 自动升 10-bit,逐帧算动态元数据写 <out>.hdrvivid.json(本机 ffmpeg 无 CUVA 编码器,SEI 由下游合入) |
实测:4K120 HDR10 流式 51 ms/帧、落盘 0;apply_fx 优化后 76→16 ms(bloom 走 1/4 分辨率)。详见 skill/reference/numpy-pillow.md。
| param | type | required | description |
|---|---|---|---|
projectDir | string | yes | project directory containing index.html / 含 index.html 的工程目录 |
mode | string | no | contact (default) · stills · video · sheet / 渲染模式,默认 contact |
out | string | no | e.g. out/video.mp4 / 输出路径,如 out/video.mp4 |
stills | string | no | comma-separated timestamps, for mode=stills / 逗号分隔的时间戳,配合 mode=stills |
width height | number | no | frame size / 帧尺寸 |
fps duration | number | no | timing / 帧率与时长 |
keys | number | no | keyframes in a contact sheet (default 12) / 联系表关键帧数,默认 12 |
audio | string | no | audio file to mux / 要合入的音频文件 |
force | boolean | no | ignore the incremental cache / 忽略增量缓存 |
keepFrames | boolean | no | keep the frames directory (it is the cache) / 保留帧目录(它就是缓存) |
workers | number | no | parallel Chrome processes, default 1 / 并行 Chrome 进程数,默认 1 |
gl | string | no | auto(default) · gpu · soft / GL 探测策略,默认 auto(仅 chrome 引擎) |
engine | string | no | chrome(default) · node / 渲染引擎;node = 无浏览器 skia 快速路径(需 @napi-rs/canvas) |
format | string | no | jpeg(default) · png / 帧容器格式;jpeg 快约 3×,png 无损 |
quality | number | no | 1-100,默认 95 / JPEG 质量 |
orientation | string | no | landscape · portrait / 未同时给宽高时的 4K 方向默认 |
depth | number | no | 8(default) · 10 · 12 / 输出色深;12 = HEVC main12(自动切 libx265) |
music_mv_lyricsWrite lyrics into a project and point project.json at them. Malformed LRC reports the line number; zero cues refuses to write. / 把歌词写进工程并把 project.json 指向它。畸形 LRC 报出具体行号,0 条 cue 拒绝写盘。
| param | type | required | description |
|---|---|---|---|
projectDir | string | yes | directory containing project.json / 含 project.json 的工程目录 |
lrc | string | yes | full LRC text, one [mm:ss.xx] cue per line / LRC 全文,一行一条时间标签 |
mode | string | no | replace(default) overwrites lyrics.lrc; append adds cues / 覆盖或追加 |
Output: { path, cues, firstCue, lastCue } (times in seconds) / 输出:路径、cue 数、首尾时间(秒)。
music_mv_diffDeterminism / difference check between two rendered frames: sha1 byte-compare first, then PSNR. Missing frames name the exact file. / 两帧之间的确定性与差异校验:先 sha1 字节比较,再算 PSNR;缺帧会报出具体文件名。
| param | type | required | description |
|---|---|---|---|
projectDir | string | yes | directory containing frames/ / 含 frames/ 的工程目录 |
a | number | yes | first frame index (non-negative) / 第一帧帧号(非负整数) |
b | number | yes | second frame index / 第二帧帧号 |
Output: { frameA, frameB, identical, psnr, note } — identical frames return psnr: null / 输出:帧号、是否一致、PSNR;完全一致时 PSNR 为 null。
music_mv_studio「MV 工坊」图形界面的入口:返回工作室地址、挂载状态、工程数量与当前渲染任务。 / The MV Studio entry point: returns the studio URL, mount state, project count and the current render job.
| param | type | required | description |
|---|---|---|---|
action | string | no | status(默认摘要)· url(只要地址)· job(只看当前任务)/ status · url · job |
没有 webServer 的 profile(例如只装 @deepseek-ai/dsh-base)里,图形界面不挂载,工具会说明原因;七个工具本身照常可用。
In a profile without webServer the GUI is not mounted and the tool says so; the other tools keep working.
宿主半边把工作室挂在 DSH 自带 webServer 的 /music-mv 前缀上;浏览器半边注册一个右侧边栏页面类型,主体是原生 DOM 的状态镜像(工程数、渲染状态、离线交接),不含 iframe、不调 window.open——所有交互都在 codeMV 弹层里完成。
The host half registers the studio under /music-mv on the DSH webServer; the browser half adds one right-sidebar page type whose body is a native-DOM status mirror (project count, render state, offline hand-off) with no iframe and no window.open — every interaction happens inside the codeMV popup.
| route | 作用 / purpose |
|---|---|
GET /music-mv/studio | 中文工作室页面 / the Chinese studio page |
GET /music-mv/api/state | 工作区 + 工程列表 + 当前任务 / workspace, projects, current job |
POST /music-mv/api/probe | 跑一次环境自检 / run the environment probe |
POST /music-mv/api/render | 开始渲染({ project, mode, width, height, duration, keys, workers })/ start a render |
POST /music-mv/api/cancel | 取消当前任务 / cancel the running job |
GET /music-mv/api/job | 任务快照(进度 + 日志尾部)/ job snapshot with progress and log tail |
GET /music-mv/api/file?path=<rel> | 预览联系表 / 成片(支持 Range、download=1)/ preview or download an artifact |
GET /music-mv/api/text?path=<rel> | 读 storyboard.md、lyrics.lrc 等文本 / read a text file |
控制面板 / the control panel(全新)—— 对话视图环标签页「MV 工坊」:首屏 hero「一键进入子代理」主色大按钮 + 四级降级说明 + 当前预设回显;下面依次排开三节:总览(状态:工作区 / 工程数 / 任务·进度·速度·预计·状态五条分条)、工程进度(每个工程一行:缩略图 + 名称规格 + 右侧原生进度条与「61% · 386/630 帧」;正在渲染的行描主色边;算不出就显示「—」不画假条——进度口径是「运行中→任务实测 pct,否则→帧缓存覆盖率 frames÷(时长×帧率)」)、渲染状态(全局进度条),再加预设区(清晰度含自定义档、风格 17 档、帧率 6 档、⇄ 横竖屏)与钉底三动作(渲染联系表 / 风格画廊 / 复制路径):

这张图是
node tools/make-panel-shots.mjs用真实lib/client.js组件(连样式都由真实代码ensureEntryStyle()注入)经迷你 React 运行时挂载后截的——结构、文案、CSS 全部来自仓库源码;状态数据是写死样例(工作区路径掩码为<workspace>、无渲染任务),所以它是组件渲染图,不是运行中 GUI 的抓图。可复现:同一条命令重跑即得。 Captured from the real component (the shot tool mountslib/client.jswith a minimal React runtime and lets the real code inject its own CSS), so structure, copy and styles come from the repository's source. The state is a fixed sample with a masked workspace path — a component render, not a live-shell capture.
工作室 HTTP 面 / studio HTTP surface(深色 / 浅色,配色跟随 DSH 的 DeepSeek 主题):

操作面只有一个:标签页 / one working surface: the tab。对话视图环里的「MV 工坊」标签页(role="region",标题即 type.label)承载全部交互——没有标题栏小按钮、没有右下角悬浮窗,也不需要展开/收起外壳:标签页本身就跟对话、轨迹平级,铺满视图区。全部用标准 DOM 标签 + DSH 设计令牌实现,不创建自定义元素、不内嵌网页。
第一屏是 hero 主舞台:**「一键进入子代理」**主色大按钮 + 「四级降级…每一级都如实回报」说明行 + 当前预设回显(如 当前预设:标准 1280×720 · DeepSeek 蓝黑 · 30 帧/秒,未选时如实写「按工程默认开工」)。点之前就知道会用什么参数开工。
状态区读 /music-mv/api/state 的实测值;接口不可达就如实写「离线」,不摆占位数据。动作:
| 动作 | 位置 | 降级链 / fallback chain |
|---|---|---|
| 一键进入子代理(hero 主按钮) | 首屏 | 宿主原生 agentTeams.spawnTeammate → POST /music-mv/api/kickoff → 当前会话发出「以子代理/队友形式开工」→ 复制开工指令到剪贴板;每一级都如实回报走到哪一级,绝不谎报成功 |
| 渲染联系表 | 钉底 | 插件自己 POST /music-mv/api/render(不经模型);没有工程就说没有,失败就报失败,渲完同源 <img> 直接预览 |
| 风格画廊 | 钉底 | 17 套预设的离线内置展示图(384×216 WebP data-URI,零网络零路由);回落链:内嵌图 → 宿主真实配色渐变 → 灰块+如实标注,绝不编颜色 |
| 复制工作区路径 | 钉底 | 剪贴板被拦时如实报「复制失败」 |
预设区:清晰度(含自定义档,宽/高 16–7680,localStorage 记忆)、风格(17 档,中文名三级回落:宿主 title 中文段 → 内置中文名 → 英文 id)、帧率(跟随工程 / 24 / 25 / 30 / 60 / 120),下拉右侧 ⇄ 横竖屏切换(先尝试对上内置档,对不上落自定义;非法输入不写进开工提示词)。三者都会写进开工提示词。
The tab is the only working surface — no title-bar chip, no floating dock, no shell to expand: it sits alongside 对话/轨迹 in the conversation view ring and fills the view. Its first screen is a hero stage with the one-click subagent button, the four-stage fallback note and a readout of the presets that will be used, so you see the parameters before you click. Below it live the measured /music-mv/api/state values (or an honest offline), the preset controls and three pinned actions.
两处原生入口 + 一处只读镜像 / two native surfaces plus a read-only mirror:
conversation.view(order:20,与「对话」「轨迹」并列),标签条按注册项自动生成、宿主 activateView 切换、仅激活时挂载;role="region"、不劫持焦点,铺满视图区,全部交互都在这里。http://127.0.0.1:<port>/music-mv/studio,或让模型调 music_mv_studio 拿地址。标题栏没有小按钮、右下角没有悬浮窗——源码有反向断言防止回归。
The only interactive surface is the MV 工坊 tab in the conversation view ring (conversation.view, order:20, mounted only while active, role="region", never steals focus); the sidebar stays a read-only native status mirror, and the HTTP studio URL remains available for browsers and the music_mv_studio tool. Reverse assertions in the tests keep the title-bar chip and the floating dock from coming back.
无服务也能用 / works without the service:没有 webServer 的 profile(headless、TUI、纯 base)里,工作室状态拿不到,侧栏状态镜像与弹层自动进入离线模式——不轮询、不发请求,「把要求交给模型」用一段以 /music-code-mv 开头的交接提示走队友/子代理降级链;渲染完成后联系表会作为图片直接回到对话里。HTTP 路由只是给状态与直渲加速的可选项,不是插件的必需品。
In a profile without webServer the studio page is absent and the sidebar body switches to offline mode: no polling, no requests, actions go straight to the model (kickoff still falls back through the teammate/subagent chain), and contact sheets return to the chat as image blocks. The HTTP routes are an optional accelerator, never a requirement.
边界 / boundaries:所有路径都解析到工作区内,.. 越界一律 403;只允许读,不在界面上改工程源码;同一时刻只跑一个渲染任务。界面路由直接挂在 webServer 上,因此只监听回环地址(127.0.0.1)。
Every path resolves inside the workspace root and .. escapes are rejected with 403; the GUI never edits project sources; one render job at a time. The routes sit on the loopback-only webServer.
17 个开箱即用的预设:一条命令拿到配色、后期、时长、分镜与占位歌词,生成即可渲染。4 个氛围 + 12 个主流 AI 模型品牌配色 + 1 个角色智能体(品牌色取自官网 CSS 与官方 logo SVG),下面是全部的实拍联系表。 17 ready-to-use presets: one command yields palette, post-processing, duration, storyboard and placeholder lyrics. Four mood presets, twelve mainstream AI-model brand palettes and one character agent (brand colours pulled from official CSS and logo SVGs); below are real renders of all of them.
claude 橙白 | deepseek 蓝黑 | doubao 蓝橙 | dusk-lofi 落日低保真 |
|---|---|---|---|
![]() | ![]() | ![]() | ![]() |
gemini 渐变 | gpt 黑白 | grok 黑橙 | ink-paper 纸墨 |
![]() | ![]() | ![]() | ![]() |
kimi 蓝 | llama Meta 蓝 | midjourney 墨蓝 | mistral 暖橙 |
![]() | ![]() | ![]() | ![]() |
neon-rain 霓虹雨夜 | phosphor 磷光终端 | qwen 紫 | zhipu 智谱 三色 |
![]() | ![]() | ![]() | ![]() |
角色智能体预设 / character agent preset —— 配色 + 风格 + 性格一次配齐,既是色表也是一位能对话的创作伙伴(persona 字段写死特质、说话方式、外形约束与工作规矩):
fairy 仙灵 / Fairy |
|---|
![]() |
node skill/scripts/init.mjs my-mv --preset=deepseek # 套用预设 / apply a preset
node skill/scripts/init.mjs --list-presets # 列出 17 个预设 / list all 17
node tools/make-docs-images.mjs # 重出上面这些图 / regenerate these images
python skill/scripts/audit-presets.py # 配色纪律测试 R1–R4 / palette discipline test
细节见 skill/reference/presets.md。
feature-demo 是一支 76 秒的全功能巡礼短片:开场 codeMV 字标弹性归位 → 16 预设色卡巡游 → 显示器家族六连拍(CRT 磷光 / VFD 荧光 / LED 点阵 / 机械翻牌 / 电子墨水 / VHS)→ 手写透视 3D 与后期通道逐个点亮 → 双引擎分屏同绘 → CRT 关机塌缩收尾。旁白由 MiMo TTS 六段合成,每段都先合成、量到真实时长再排分镜,最后按镜头起点摆位混成一条音轨。
规格 / spec —— 母版 3840×2160 · 120fps · AV1 + AAC · 76.00s(953 MB,属渲染产物,不入库);下面是派生的文档小样 1280×720 · 30fps · h264 · 2.2 MB,已按体积纪律放进 reference/video/。
分镜联系表(12 张等距关键帧,自检用)/ storyboard contact sheet:

node skill/scripts/init.mjs feature-demo --preset=deepseek # 脚手架 / scaffold
node skill/scripts/tts.mjs --batch=narration.json # 六段旁白 / six narration clips
node skill/scripts/render.mjs --project=feature-demo --contact --w=480 --h=270 # 自检 / self-check
node skill/scripts/render.mjs --project=feature-demo --out=out/video.mp4 \
--audio=narration/track.wav --workers=10 # 成片 / master
机器自检会把三处分开报:webgl(能力门)、gpu (hardware GL)(实际在用的显卡)、encoder (live test)(真编一帧后的编码器)。详见 skill/reference/techniques.md 的 GPU 加速一节。The probe reports WebGL capability, the GPU actually in use, and the encoder after a real test frame — see the GPU section in techniques.md.
showcase-3d 是一支全 3D 的 31 秒短片,用新的 fairy 预设配色,按 reference/threejs.md 的豪华场面清单逐条打底——共享渲染器 + EffectComposer + UnrealBloom,2D 只负责排版与收边(fx: FX3D 关掉重复的 bloom/色差/扫描线):
Six shots, all WebGL: ① 30k-particle nebula with an expanding shock ring → ② 120 instanced emissive pillars over a mirrored floor, lit wave sweeping through → ③ a glowing ring tunnel with a gate flash → ④ a metallic knot with two sweeping lights → ⑤ seven halos snapping into alignment under a crisp codeMV title → ⑥ 24k particles collapsing to an ember, then a clean power-off.
| # | 镜头 / shot | 事件 / the one event |
|---|---|---|
| 1 | 粒子星云 swarm | 金色冲击环从中心扩出 |
| 2 | 发光柱林 pillars | 点亮波沿 z 推进,柱子逐根亮起 |
| 3 | 发光隧道 tunnel | 相机穿过的门环炸亮 |
| 4 | 金属结 knot | 高光扫过表面 |
| 5 | 光环阵 halo | 七环对齐到同一平面 |
| 6 | 坍缩 collapse | 塌成一点余烬 → 关机 |
3D 联系表 / storyboard contact sheet:

实测性能 / measured performance:930 帧 720p → 17.3s(8 workers,约 54 fps 吞吐、18.5 ms/帧),编码器日志自动带 [hardware verified — test frame encoded]。同一分辨率下 Canvas2D 全 FX 约 255 ms/帧——走硬件 GL 的 3D 反而更快,因为 FX3D 把重复的2D 后期关掉了,辉光在 GPU 上由 UnrealBloom 一次做完。930 frames of 720p in 17.3s (8 workers, ~54 fps, 18.5 ms/frame) versus ~255 ms/frame for Canvas2D with the full 2D FX chain — hardw