feat(rust): first green Rust crates — oakcore-rs, oakundo, oakcommon, oaktimeline, oakaudio

- oakcore-rs: Rational/TimeRange/TimeRangeList/PixelFormat/SampleFormat
  mirroring oakcore C++ semantics (91.6% coverage)
- oakundo: UndoCommand/UndoStack with vtable commands (99.6%)
- oakcommon: config/XML/VideoParams/logging etc. (89.8%, 516 tests);
  XML via quick-xml, logging via the log facade
- oaktimeline: markers/workarea/edit commands (95.4%)
- oakaudio: processor/manager/sync/waveform/levelmeter (88.4%)
- unified -MMCCCC error codes across all C ABI headers (registry in
  include/common/error.h; pass-through rule)
- olive::Variant moved node -> common (cross-module value type)
- oakcommon_videoparams_set_is_3d added to the C ABI
- M11 (oakplugin Rust rewrite) plan + notes.md freeze line for the
  inter-module C ABI wiring
This commit is contained in:
2026-08-09 04:24:28 +08:00
parent 5a564f30ca
commit 4b24aa9d67
138 changed files with 47853 additions and 48 deletions
@@ -46,6 +46,7 @@
| M7 | oakrender | `render/`(Renderer/PlaybackCache/ColorManager/帧缓存/job) | render→node 38、render→codec 9、render→task 2、render→undo 1、render→src 1(违规) |
| M8 | oaktask | `task/`(Task/TaskManager/项目任务编排/cache 任务;**工程文件 IO 已划给 oakstorage,M3a**) | task→node 38、task→codec 4、task→render 4、task→storage(load/save 委托) |
| M9 | oakplugin | `pluginSupport/`(OpenFX host) | plugin→node 6、plugin→render 6、plugin→undo 2、plugin→coreengine 2 |
| M11 | oakplugin Rust 重写 | 整个模块换 Rust(含自研 OFX host 取代 HostSupport;0-2 期已立项:golden master、crate 落地、GL+ofxColour+render 驱动收编) | 手册 M11;项目 Rust 化滩头模块,完成后 node/render→plugin C++ 引用归零 |
| — | liboakengine | `src/capi` + `coreengine` + `tool/` + `ui/` 残余 | 纯装配层:facade 内部调用改经各模块 C ABI(或保持现状直接链,见 M9 §4 裁决) |
**M3.5(伴随 M3 的类型下沉)**:`render/videoparams.h`、
+143
View File
@@ -0,0 +1,143 @@
# M11:oakplugin Rust 重写(含自研 OFX Host)
> 前置:M9(oakplugin 拆分)已完成,模块边界已是纯 C ABI
> (`include/plugin/*.h`,引用计数句柄)。本计划把 oakplugin 的
> **整个实现**换成 Rust:自研 OFX 宿主取代 openfx HostSupport
> (third_party/openfx/HostSupport,vendored 1.3 万行 C++),六个
> C++ 类(4485 行)语义级移植;pluginrenderer(oakrender 内,
> 1857 行)在第 2 期收编。
>
> 这是项目全量 Rust 化的滩头模块:第一张 Rust crate 模板
> (构建、测试、FFI 纪律、泄漏断言)在此确立。
>
> 本文档覆盖已立项的 0-2 期;OpenCL(3 期)与 UI 类 suite
> (Interact/Dialog/Draw,4 期)届时评审再立项。
## 0. 形态决策(2026-08,已定)
- **单一 Rust crate 收编整个模块**,不自起独立 host 库。crate
内部 host 核心与实例/参数/纹理桥之间是普通 Rust 模块调用
(safe,编译器查所有权),只有两条 FFI 边界:
1. 对 OFX 插件:fetchSuite/action,规范定义的 C ABI;
2. 对 oak 其余模块:`include/plugin/*.h`,M9 已冻结。
- 上游零感知:oaknode 的 PluginNode、facade、oakrender 都只认
C ABI,不关心实现语言。
- HostSupport 删除后,liboaknode/liboakrender 对 liboakplugin 的
C++ 符号引用(PluginCache、Property::Set 等 30+ 个)归零。
- unsafe 收敛在两层:suite trampoline(插件调入)与 C ABI 导出
(oak 调入)。其余全部 safe Rust。
- 每个 FFI 入口必须 `catch_unwind` 兜底并映射为负错误码(01 §0),
构建保持 `panic = "unwind"`。
## 1. 保留与消耗的资产
- `include/plugin/{error,host,instance}.h`:公共契约,不变。
- 六个 C++ 类的语义(olivehost/oliveplugininstance/oliveclip/
paraminstance/image/pluginprogressreporter):移植,不重新设计。
- pluginrenderer.cpp 的渲染流程语义:2 期收编时逐段对照。
- HostSupport:仅作行为参照系,不进构建。
## 2. 第 0 期:Golden master 与测试基建(约 1 周)
不改实现,产出验收门槛。
### 2.1 插件内省 C ABI(include/plugin/instance.h 新增,约 15 函数)
参数枚举(名称/OFX 类型/标签/hint/父组/坐标系/secret/
display min-max/choice labels-values-order/按类型默认值)、clip
枚举(名称/标签/可选性/分量/位深)、描述符(标识/label/描述/
上下文集)。PluginNode 同期改走该 C ABI,node→plugin C++ 引用
归零。行为必须逐行不变。
### 2.2 描述符快照
系统三 bundle(CImg/Misc/Shadertoy)全量 dump 成 JSON 入库
(tests/ofx/snapshots/):标识、上下文、参数矩阵(默认值与
choice 排序结果)、clip 描述、**getClipPreferences 协商后结果**
(分量/位深/像素比——隐式行为主要来源)。后续每期 diff 必须为空。
### 2.3 渲染 golden master
代表性效果集(CImg invert/blur、一个 2D 变换、一个 Shadertoy、
一个 generator):固定输入帧,CPU 与 GL 两路径,全链路
**F32 + ACEScg**,存 EXR + SHA256。CPU 路径要求 bit 级一致,
GL 允许 1e-4 容差;GL 用例仅本机,CI 跳过。
### 2.4 最小测试插件
~300 行 C 测试插件(filter 上下文,覆盖常用参数类型与双 clip),
供 suite round-trip 单测与 1 期首测。
## 3. 第 1 期:Rust crate 落地(约 4-5 周)
### 3.1 范围
- host 核心:Property/Memory/ImageEffect v1/Parameter(12 种类型)/
Message v1v2/Progress v1v2/TimeLine v1/MultiThread v1 suite;
filter/generator/transition 上下文;describe/describeInContext/
createInstance/destroyInstance/getClipPreferences/getRoD/getRoI/
isIdentity/render(CPU)/begin-endSequenceRender 动作。
- 六类移植:host 单例(扫描/缓存)、instance 包装、clip↔纹理桥
(经 oakrender C ABI)、param↔node 桥(经 oaknode/oakundo C
ABI)、image 包装、progress reporter。
- 语义重灾区(逐条对照 HostSupport 并注释):clip 分量/位深/
像素比协商顺序;RoD/RoI;isIdentity 短路;field 透传。
### 3.2 crate 结构(src/plugin/rust/)
声明底稿已入库,见该目录。模块划分:ffi(C ABI 导出)、handle
(句柄脚手架)、host(扫描/缓存/suite 注册)、descriptor、
instance、clip、image、param、property、suites/*(八张函数表)、
bridge/{node,render,undo}(oak C ABI 导入)。
### 3.3 构建
crate 出 staticlib;CMake 经 corrosion(或自定义 target)链成
liboakplugin;standalone 树接线不变;CI 加 rust toolchain(版本
pin 在 rust-toolchain.toml)。
### 3.4 明确不做
GL 路径、ofxColour(均 2 期);OpenCL(3 期评审);
Interact/Dialog/Draw(4 期);Parametric(按需)。公共 C ABI 不变。
### 3.5 验收
- 0 期 golden master 全绿(描述符 diff 空、CPU 渲染 bit 一致)。
- 测试插件 round-trip + CImg 全量插件 describe/协商冒烟。
- 生命周期:createInstance/destroyInstance 配对、重复 scan、
render 中途 cancel;alive 计数无泄漏。
- nm:liboaknode/liboakrender→liboakplugin C++ 符号为 0;
HostSupport 移出构建。
- cargo test(host 内部单测)+ ctest(C ABI 层)双绿。
## 4. 第 2 期:GL 路径 + ofxColour + pluginrenderer 收编(约 2-3 周)
- OpenGLRender suite v1(clipLoadTexture/clipFreeTexture/
flushResources)与像素深度协商;纹理桥沿用 M9 的
wrap_native/readback 机制。
- ofxColour:属性族 + GetOutputColourspace action;ACEScg 工作
空间经 OCIO config 属性告知插件。
- pluginrenderer.cpp 语义收编进 crate(render driver 模块),
oakrender 的 PluginJob 退化为一次 C ABI 调用;oakrender 彻底
不碰 OFX。
- 验收:GL golden master 本机绿;ofxColour 属性 round-trip 单测 +
支持插件端到端用例;F32+ACEScg 链路保持绿。
## 5. 依赖与顺序
- 开始前:③ 剩余机械部分(render→node、→common、transition
清理)收尾。
- 2.1 完成后 ③ 的 node→plugin 依赖清零;render→plugin 的 12 个
符号随 1 期 HostSupport 删除消失。
- 每期结束独立评审再进下一期。
## 6. 风险
- **语义边角**:HostSupport 隐式行为无法全覆盖;缓解:快照含
协商后结果 + CImg 全量协商冒烟。
- **FFI 纪律**:panic 越界/回调线程模型是新坑;缓解:catch_unwind
全入口覆盖、共享状态全部 Mutex、回调线程注册表。
- **GL 本机依赖**:GL 验收需人工本机确认。
- **首个 Rust 模块**:构建模板(corrosion、双测试层、alive 计数)
要为后续模块立好,宁慢勿滥。
+119
View File
@@ -395,3 +395,122 @@ ColorManager)去Qt化过程中的删除与语义变化,迁移调用方时需
oaknode 的 35 个 TU,析构引用 olive::Texture::~Texture——值系统
载荷问题,与 UndoCommand 跨模块继承同类,留待值系统重做。
除此之外 liboaknode→liboakrender 的 C++ 符号引用为 0。
## ③ C ABI 接线冻结线(2026-08-07)
③(模块间调用切 C ABI)完成两整块后冻结,剩余部分改由各模块的
Rust 重写驱动(RIIR 后调用方只能走 C ABI,接线自动发生且被类型
强制做对):
- 已切:oaknode→oaktimeline(81431d180)、oaknode→oakrender
(5a564f30c,缓存体系/色彩/单例全部 C ABI 化)。
- olive::Variant 从 oaknode 下沉到 oakcommon(src/common/src/
variant.{h,cpp})——它本是跨模块值类型。
- 冻结时点的残留(nm 可查):
- render→node 41 个 C++ 符号:ProjectCopier 深拷贝族、
NodeTraverser(RenderProcessor 跨模块继承)、ColorManager、
Footage/MultiCam/ViewerOutput 常量、oaknode_c_api::alive_*。
→ 在 oaknode/oakrender Rust 重写中消解(架构底稿:
src/node/rust、src/render/rust;ProjectCopier 反转为
oaknode_project_deep_copy/sync_copy,traverser 改 hook 制)。
- node/render→plugin 的 OFX C++ 符号:随 M11(DeepSeek 实现中)
落地消解。
- 各模块→oakcommon 的 XmlStreamReader/FileFunctions/VideoParams
C++ 调用:随 oakcommon Rust 化消解。
- 文档化例外:Texture 的 Variant 载荷(Rust 侧不存在此问题)、
UndoCommand 跨模块继承、oakgl2/oakvulkan 后端插件接口。
- ④(隐藏 C++ API)同步搁置:模块 Rust 化后 C++ 符号自然消失。
## oakcommon Rust 测试:ffmpeg_bridge 符号依赖(2026-08-08)
`oakcommon_ffmpegutils_get_compatible_bridge_pixel_format` 在非测试构建
中会经 `find_best_pix_fmt_of_list` 引用 ffmpeg_bridge 的
`fb_find_best_pix_fmt_of_list` 符号。集成测试二进制不链接 libffmpeg_bridge,
实验证实直接调用该 FFI 导出会在链接期报 `_fb_find_best_pix_fmt_of_list`
undefined symbol(cargo test --test link_exp 复现,随后已删)。
因此该函数不能走普通集成测试。处理方式(与 oakplugin/oaktimeline 的
test-stubs 约定一致):
- `src/common/rust/Cargo.toml` 新增 `test-stubs` feature;
- `find_best_pix_fmt_of_list` 的 extern 声明/调用改为
`#[cfg(all(not(test), not(feature = "test-stubs")))]`,桩改为
`#[cfg(any(test, feature = "test-stubs"))]`——`cargo test --lib`
无需 flag 仍走桩,集成测试 `cargo test --features test-stubs` 也可链接;
- `tests/ffi_ffmpegutils.rs` 覆盖 ffmpegutils 全部 6 个 FFI 导出
(成功路径 + null out-param 的 E_INVALID 路径),需带
`--features test-stubs` 运行;不带 flag 时该文件整体为空(cfg 门控)。
## oakcommon Rust:ocioutils/oiioutils 吸收 oakoci(2026-08-08 接手笔记)
### 基线
`src/common/rust` 全量测试:`cargo test --features test-stubs` **497 个全部通过**
(314 lib + 12 contract + 8 ffi_colortransform + 61 ffi_commandlineparser +
24 ffi_config + 8 ffi_ffmpegutils + 21 ffi_misc + 12 ffi_subtitleparams +
15 ffi_videoparams + 22 ffi_xmlutils + 集成 real_ocio 等)。TODO 中旧快照
"476"已过时;后续验收以"保持 497+ 全绿"为准。
### oakoci shim crate 的拆解去向
`src/bindings/oakoci/`(untracked,纯 Rust shim,曾用于给
oakrender Rust 提供 OCIO/图像能力)内容已全部内收进
`src/common/rust`,然后整目录删除:
| oakoci 内容 | 去向 | 落点 |
|---|---|---|
| `oci.rs` OcioConfig / OcioProcessor(包 `ocio_rs::Config`/`CPUProcessor`) | 重写为 `ocioutils.rs` 的 `OcioConfig`/`OcioProcessor`,错误从消息包装 `Error` 改为 `crate::error::Error::Failed`(新增 `From<ocio_rs::OcioError>`) | `src/common/rust/src/ocioutils.rs` |
| `image.rs` F32Image + read/write_image_f32(image 0.25 tiff) | 原样并入 `oiioutils.rs`,错误统一走 `Error::Failed` | `src/common/rust/src/oiioutils.rs` |
| `error.rs` 消息包装 Error | 并入 `error.rs`(`Error::new` 便捷构造) | `src/common/rust/src/error.rs` |
| `tests/smoke.rs`(5 OCIO + 1 TIF round-trip) | 移植为 `tests/real_ocio.rs`,config 用 `env!("CARGO_MANIFEST_DIR")/../../../engine/render/ocioconf/config.ocio`,断言改为 `Error::Failed(_)` | `src/common/rust/tests/real_ocio.rs` |
### 位深判别值(已验证)
`ocio_rs::BitDepth` 是 `#[repr(i32)]`,判别值即 OCIO 码:Unknown=0,
Uint8=1, Uint10=2, Uint16=5, F16=7, F32=8。`ocioutils.rs` 直接
`depth as i32`,单测锁死 1/2/5/7/8/0,与 C++ 的
`OCIOUtils::get_ocio_bit_depth_from_pixel_format`(CPP-PARITY)一致。
### 决策:不引入 ffmpeg-next(偏差,已落定)
TODO 原计划用 `ffmpeg-next` 的 `av_d2q` 做 aspect-ratio 换算。复查发现
C++ 侧 `Rational::from_double`(`core/src/util/rational.cpp:39`)本身就是
**av_d2q 的移植**(文件内注释 "ported from FFmpeg's av_d2q"),oakcore-rs
的 `Rational::from_double` 与之对等,且现有 12 个单测已锁死行为
(NaN/超界→(0,0)、0.0→(0,1)、约分)。因此**保留手写移植,走
`oakcore_rs::Rational::from_double`**,不把 ffmpeg-sys-next(bindgen +
系统 FFmpeg)拖进叶子 crate——守住 README 的 "narrow extern C" 纪律。
`oiioutils.rs` 的实现已如此;文档残留的 "via ffmpeg-next" 说法待清。
### OCIO 环境依赖
`ocio-sys` 不自探测系统 OCIO:无配置时构建的是 stub bridge(调用全失败)。
`src/common/rust/.cargo/config.toml`(从 oakoci 抄入)固定三个变量:
`OCIO_RS_ENABLE_REAL=1`、`OCIO_INSTALL_DIR=/opt/homebrew`、
`OCIO_RS_LINK=dynamic`。机器上 OpenColorIO 2.5.2(Homebrew)、
libavutil 60.26.102、~/.cargo/registry 已缓存 ocio-rs/ocio-sys/
image/ffmpeg-next 全套。
### 待办(后续会话)
1. 全量验证:`cargo test --features test-stubs` ≥497 绿;`cargo build --release`。
2. 清文档残留:`oiioutils.rs` 模块注释与 `README.md` 依赖表/决策 6 的
"ffmpeg-next" 说法改为 oakcore-rs 手写移植。
3. `rm -rf src/bindings/oakoci`(已确认无 CMake/workspace/源码引用,仅
oakotio/oakaudioout 两个 sibling README 提到,删前先改)。
4. `cargo tarpaulin --features test-stubs` 覆盖率 ≥80%(tarpaulin 默认只
跑 lib 单测,新并入代码已带模块内单测,见 ocioutils.rs/oiioutils.rs)。
5. 不做任何 git 提交;分支 refactor/split-oakengine 上的既有暂存改动
(variant.cpp/h 移动、include/*/error.h)不触碰。
## 技术债登记(2026-08-09)
- **oaktask 渲染循环同步化**:src/task/rust/src/render.rs 目前是
"一帧一 ticket、wait 到底"的同步循环(C++ 原版是最多
hardware_concurrency 个 ticket 并发 + 完成队列 + condvar)。
可观察契约不变但导出/预缓存吞吐下降。修复方案:oakrender
ticket arena 本就支持多 ticket 在飞 + 回调,改为 N 并发 +
按序交付,并发度参数照 C++。待 render crate 落定后立即派代理
补上(测试已就绪)。
- **oaktask 导出缺口**:临时文件重命名(失败不留半成品)与
sidecar 字幕编码器未实现;precache 缺项目深拷贝。