六个 crate 的分工:compositor、effects、gpu、masks、time、bridge

2026-08-09

先说清楚版本:本文描述的是 OpenCut 旧版(classic)的行为。该代码库已归档、不再维护,opencut.app 线上目前仍运行该版本;重写版正在开发中,功能与操作可能变化。核对日 2026-08-09。文中所有关于六个 crate 的说法,都只对 OpenCut-app/opencut-classic 成立,不能拿去描述重写版仓库 OpenCut-app/OpenCut

看一个项目的 Rust 目录,最没用的读法是把每个 crate 的名字翻译一遍然后当作结论。名字只是作者的意图签名,真正能拿来做决定的,是「这个划分让哪一类改动只需要动一个地方」。这篇就按这个思路,把 OpenCut classic 的 rust/ 拆开讲。

目录里到底有什么

classic 仓库的 rust/crates/ 下是六个 crate:bridgecompositoreffectsgpumaskstime。同级还有一个 rust/wasm/,它被发布为 npm 包 opencut-wasmapps/web 的依赖清单里就有这一项。

classic 的 README 对 rust/ 只给了一句定位:平台无关的核心,包含 GPU 合成器、特效、蒙版、WASM 绑定,并且他们正在把业务逻辑从 TypeScript 迁移过来

把这句话和目录对一下,你会得到一张不完全对齐的表:

crateREADME 那句话里的对应项我们的依据强度
compositorGPU 合成器名字与 README 措辞直接对应
effects特效名字与 README 措辞直接对应
masks蒙版名字与 README 措辞直接对应
bridgeWASM 绑定(与 rust/wasm/ 一并)措辞对应,但边界未见明确文档
gpu未单列有额外的构建配置线索,见下文
time未单列只有名字,无文档对应

我要强调最后一行:README 那句只点了四件事,time 在里面没有对应词。我们读的是目录,没有读这几个 crate 的源码,所以这篇不会告诉你 time 里有什么。如果你在别处看到有人绘声绘色地讲 OpenCut 的时间轴 crate 如何实现,先问一句依据是什么。这也是判断依据本身的一部分:一个 crate 名字能不能支撑结论,取决于有没有第二个来源印证它。

读懂这套划分的钥匙:TS 和 Rust 各拥有什么

classic 的 docs/effects-renderer.md 里有一句分工边界写得非常干净,它是理解整个 rust/ 目录的钥匙:

所有特效共用同一个 GPU 渲染器。TypeScript 决定跑哪些 shader 标识符、传哪些 uniform;Rust/wgpu 拥有设备创建、纹理和 pass 执行。

这句话把「配置」和「执行」切开了。配置留在 TypeScript:一个特效长什么样、暴露哪些参数、需要几趟渲染,全部由 TS 侧的 EffectDefinition 描述——typenamekeywordsparamsrenderer 这几个字段,都是写在 apps/web/src/lib/effects/definitions/ 下的普通 TS 文件里的。执行留在 Rust:设备、纹理、pass 的实际跑动。

这条边界能立刻回答一个高频问题:加一个新特效要不要写 Rust? 按文档给的三步,不用——在 apps/web/src/lib/effects/definitions/ 建文件、导出一个 EffectDefinition、在同目录 index.ts 注册,三步都在 TS 侧。多趟渲染也是在 TS 侧描述的:renderer.passes 是数组,单 pass 特效(比如调色)只有一个条目;当特效必须处理自己的输出时就需要多 pass,文档举的例子是模糊(先横后纵)、辉光 bloom(提取 → 模糊 → 合成)这类。pass 数量随参数变化时,加一个 buildPasses 函数返回带预计算 uniform 的 pass 列表。

这里有个容易踩的规则,原文写得很明确:buildPasses 存在时,所有渲染路径都用它,静态 passes 数组不再参与,只作为结构参考与兜底。也就是说你把 buildPasses 写歪了,静态数组不会来救你。文档另有一节强调 pass 解析必须走 resolveEffectPasses

所以,effects 这个 crate 的存在,不代表特效定义在 Rust 里。名字和职责在这里恰好是错位的,这正是「不要照着名字下结论」的现实例子。

gpu 为什么单独成一个 crate

六个 crate 里,gpu 是我们能拿到额外硬线索的那个。它的 Cargo.toml 里有一段 [target.'cfg(target_arch = "wasm32")'.dependencies]——针对 wasm32 目标声明了单独的依赖。

这一段说明的事情很具体:同一套 GPU 代码需要同时服务两个目标。一个是浏览器,apps/web 是 Next.js 应用,Rust 核心通过 rust/wasm/ 编译出的 opencut-wasm 进去;另一个是原生,classic 的 apps/desktop/ 是用 GPUI 构建的桌面应用,README 给它标了 in progress

把跨目标差异收进一个 crate,好处是它只在一个 Cargo.toml 里体现。对你的实际意义是一条定位规则:遇到「浏览器上行为和桌面上不一致」这类怀疑,先去看 gpu 的 target 段,而不是先翻 compositor 反过来,如果你的改动跟目标平台无关,那大概率不该动 gpu 的这段配置。

要提醒的是,这条只是构建配置层面的事实。我们没有编译过任何版本的 OpenCut,gpu 里具体分了哪些后端、wasm 与原生路径怎么收敛,本文一个字都不写。

bridgerust/wasm/ 与那条你必须知道的本地链路

bridgerust/wasm/ 一起对应 README 里的「WASM 绑定」。它们各自的边界在哪,我们没有见到明确文档,所以不下判断;但有一件与它们直接相关、且完全有依据的事:默认情况下,apps/web 用的是 npm 上的 opencut-wasm,不是你本地编译的那份。

classic 的 README 把这条单列成「本地 WASM 开发」,并注明只有在改 rust/wasm 且希望 web 用你本地构建时才需要。一次性前置:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh   # Rust 工具链
cargo install wasm-pack                                          # 构建 WASM 包
cargo install cargo-watch                                        # 供 bun dev:wasm 监听文件变化

然后是四步:

bun run build:wasm                     # 1. 从仓库根构建一次
cd rust/wasm/pkg && bun link           # 2. 注册生成的包
cd apps/web && bun link opencut-wasm   # 3. 把 apps/web 链到本地包
bun dev:wasm                           # 4. 改动时重新构建

第 2、3 步是这条链路的关键:没有这两步,你改了 Rust、重新构建了,web 侧照样跑的是发布版本的包。 这类「改了没生效」的问题排查起来最费时间,因为它不报错。判断依据也很朴素——先确认 bun link 做过,再去怀疑代码。

顺带说,只做 web 完全不需要碰这条链路:classic 的常规起法是复制 apps/web/.env.exampleapps/web/.env.local,用 docker compose up -d db redis serverless-redis-http 起数据库和 Redis(README 说 Docker 可选但推荐,只做前端可以跳过),然后 bun installbun dev:web,应用在 http://localhost:3000。桌面端同样是 opt-in 的,要做 apps/desktop 才需要先装 Rust 工具链再装桌面原生依赖。

这套划分什么时候不再是有效依据

三件事必须放在一起看,否则容易把结论用过期:

第一,边界本来就在移动。 README 自己说的是「正在把业务逻辑从 TypeScript 迁移过来」,是进行时。今天 TS 拥有的东西,作者的计划里有一部分是要往 Rust 挪的。

第二,仓库已经归档了。 OpenCut-app/opencut-classic 的状态是已归档、不再维护,最后一次推送在 2026-05-17。归档意味着不再接受提交与 issue。对你的实际影响是:上面那句「迁移进行中」不会再往前走了,六个 crate 的划分就冻结在最后这次推送。代码是 MIT 许可,仍然可以 fork 自行维护,具体条款以官方 LICENSE 原文为准。

第三,别把这张图套到重写版上。 重写版主仓 OpenCut-app/OpenCutCargo.toml 里,workspace 只有 apps/desktop 一个成员,crates/*被注释掉的apps/desktop/src/ 下是 Rust + gpui,只有 main.rsshell.rstheme.rs、7 个 components/ 与 4 个 panels/。也就是说,本文讲的六个 crate 是 classic 的现状,重写版仓库当前并没有这套 crate 划分。重写版 README 明写「OpenCut 正在被从头重写」,它列出的 Editor API、第三方插件、桌面与移动与浏览器共用一套代码库、MCP server、无头模式、编辑器内脚本标签页,都是官方 README 声明的计划、尚未发布,我们没有见到可用实现——不要把其中任何一条当成 OpenCut 今天已有的能力。重写版还没准备好接受外部贡献。这里不对进度、也不对重写这件事本身作任何评价。

一句话的落点

如果你只带走一条:在 classic 里,判断改动落点先看「配置还是执行」——配置在 TypeScript,设备、纹理、pass 执行在 Rust/wgpu,这条来自官方文档,比六个 crate 的名字可靠得多。crate 名字用来缩小范围,官方文档那句边界用来做决定。

另外,classic README 给的三条项目理由(Privacy、Free features、Simple)属于项目方自述立场,不是我们的评价,也不构成对任何商业产品的判断。README 里还感谢了 Vercel 与 fal.ai 对开源软件的支持,如实记一笔。

延伸阅读


本文依据 OpenCut 官方仓库(github.com/OpenCut-app/OpenCut 与已归档的 github.com/OpenCut-app/opencut-classic)的 README、docs/ 架构文档、package.jsonCargo.tomlrust/crates/ 目录整理,核对日 2026-08-09。本文内容为仓库源码与文档口径,我们没有编译或运行过任何版本的 OpenCut。

本文描述的是 OpenCut 旧版(classic)的行为。该代码库已归档、不再维护,opencut.app 线上目前仍运行该版本;重写版正在开发中,功能与操作可能变化。

许可条款请以官方 LICENSE 原文为准,本文不构成法律意见。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。