feat(oakplugin): GL render bridge, color picker, push-button action, worker progress, Interact host

- gl_bridge: macOS CGL offscreen context (process-wide singleton,
  serialized GlGuard), real GL output textures/FBOs, glReadPixels
  readback with vertical flip and format conversion; use_opengl now
  really engages for OpenGLRenderSupported plugins (verified with real
  GL rendering: C smoke 11/11, unit tests, GL e2e).
- OfxColor: color params get a swatch button plus a real picker popup
  (RGBA sliders, live preview, hex input, undoable commit) replacing
  the four spinboxes.
- Push buttons route kOfxActionInstanceChanged (UserEdited) per the
  OFX contract; test plugin asserts the callback.
- Worker-side plugin progress flows to the main-process progress
  dialog over the NDJSON control channel, with cancel propagation.
- OFX Interact host: NewInteract/Describe lifecycle, Draw/Pen/Key/Idle
  action surface with proper in-args, DrawSuite v1 host implementation
  sharing the gl_bridge context; interact test plugin verifies the
  event stream and real GL drawing.
This commit is contained in:
2026-08-19 22:14:54 +08:00
parent 29696479b5
commit b4ceaa9cab
28 changed files with 5481 additions and 161 deletions
+100 -16
View File
@@ -19,6 +19,8 @@ src/
property.rs memory.rs image_effect.rs param.rs
message.rs progress.rs timeline.rs multithread.rs
gl_render.rs OfxImageEffectOpenGLRenderSuiteV1(M11 §4 新增)
draw.rs OfxDrawSuiteV1(OFX 1.5,interact 绘制;真实 GL 渲染)
interact.rs OfxInteractSuiteV1 + Interact 宿主对象(interact 宿主)
host.rs Host 单例:bundle 扫描、插件缓存、action 分发
descriptor.rs EffectDescriptor/ClipDescriptor(describe 的产物)
instance.rs Instance:action 调用面(render/协商/RoD/RoI/isIdentity/
@@ -92,22 +94,40 @@ src/
OpenGLTextureTarget/PixelDepth/Components/PreMultiplication/
RenderScale/PixelAspectRatio/Bounds/ROD/RowBytes/Field/
UniqueIdentifier);存活表持有 Box<PropertySet>(句柄地址稳定)。
Output clip 的渲染目标绑定由调用方契约保证(等价 C++
`attach_output_texture`);clipFreeTexture 对 Output 不删纹理。
**OpenGLTextureIndex 是真实 GL 纹理名**(GL 模式;0 = CPU 回退)。
Output clip 的渲染目标绑定由宿主完成(等价 C++
`attach_output_texture`);clipFreeTexture 对 Output 不删纹理(宿主
回读后删除)。
- GL render action:`Instance::render_gl`(与 CPU `render` 并存)——
action 序列 kOfxActionOpenGLContextAttached → render(in args 带
kOfxImageEffectPropOpenGLEnabled=1)→ OpenGLContextDetached;
GL 模式无 CPU 输出回读,渲染结果留在附着纹理上。
渲染结果留在 FBO 附着的输出 GL 纹理上,render 返回后宿主
**glReadPixels 回读**装帧(方案 B,见下)。
- **GL 上下文规则**(ofxGPURender.h "OpenGL Current Context"):宿主
只在 Render/Begin/EndSequenceRender/Attach/Detach 期间要求上下文
current——本实现的约定是调用方(oakrender PluginJob 路径)在
进入 render_job 前把渲染器上下文置为 current 并附着输出纹理
(文档见 include/plugin/instance.h 的新增声明)。
current——本实现的约定是 render 驱动一次 `gl_bridge::acquire` 整个
GL render action(上下文 current 到 guard drop),suite 回调期间
恒 current。
- 格式协商:插件描述符声明 kOfxImageEffectPropOpenGLRenderSupported
("false"/"true"/"needed")与 kOfxOpenGLPropPixelDepth(可选位深
列表);宿主 `pick_gl_pixel_depth` 按管线 F32 约束选型(声明列表
不含 Float → GL 模式不可行,回退 CPU)。use_opengl 决策在
render_driver。
render_driver(插件 GL 声明 + 深度协商 + 桥可用)。
### GL 互操作桥(方案 B 落地,`gl_bridge.rs`)
- **macOS 真实实现**(Core OpenGL / CGL,`OpenGL.framework` 直链,
无新 crate):进程级共享离屏上下文(3.2 core profile、offline
renderer 允许),`acquire()` 全局串行 + 置当前线程 current。为每次
GL 渲染建输出 GL 纹理(尺寸 = 目标帧;F32 → RGBA32F、U8 → RGBA8)
+ FBO 挂载;插件直接画进 FBO;render 返回后 `glReadPixels` 回读
(垂直翻转、U8 归一化 F32),与 CPU 路径输出格式一致。
- 输入 clip 在 GL 模式经桥上传为真实 GL 纹理(clipFreeTexture 删除)。
- GL 失败回退 CPU(对齐现有失败语义)。Linux(EGL)/Windows(WGL)
预留 cfg stub。
- 验证:`gl_bridge.rs` 单元测试(clear 已知颜色 → 回读逐像素断言,
`OAK_GPU_TESTS` 门)+ `tests/gl_render_test.rs` 端到端(GL 测试插件
真实走 GL 路径、输出已知颜色)。
### ofxColour(OFX 1.4)
@@ -150,6 +170,63 @@ src/
(输入 clip 纹理)两个 POD 随附。GL 契约(上下文 current + 输出
附着)见头文件文档注释。
## OFX Interact 宿主(interact + Draw suite,M11 §5)
插件自定义 UI 的宿主侧:Interact 实例在主进程创建(UI 事件宿主),与
worker 进程里的渲染实例并存(OFX 允许同一插件多实例)。
- **`suites/interact.rs`**:
- `Instance::new_interact`(任务契约):取插件声明的 overlay interact
入口(kOfxImageEffectPluginPropOverlayInteractV2 优先、V1 次之;未
声明则插件 main entry)→ 建 `Interact`(属性表 + tagged INTERACT
handle)→ 发 `kOfxActionNewInteract`。插件返回 OK → Some;返回
ReplyDefault 且未声明 overlay 入口(无 interact)→ None;错误 → None。
- 生命周期:`describe`(kOfxActionDescribe == kOfxActionDescribeInteract)
→ `create_instance`(kOfxActionCreateInstance)→ 事件 → `destroy`
(kOfxActionDestroyInstance);**与效果实例同生命周期**——实例销毁
(`Instance::notify_destroy`)时先连带销毁 interact。
- 调用面(宿主→插件,返回值如实透传):`draw(viewport, pixel_scale,
time, background)`、`pen_motion(pen_pos, pen_down)`、`pen_down`、
`pen_up`、`key_down(key_sym, key_string)`、`key_up`、`idle`(任务契约
的 kOfxInteractActionIdle,属宿主扩展)、`gain_focus`/`lose_focus`。
pen 坐标为视口像素,inArgs 另带 canonical 位置(viewport/像素比)、
viewport 位置、压力(两态笔映射 0/1);key 带 kOfxPropKeySym
(ofxKeySyms.h 关键码)+ kOfxPropKeyString。返回 kOfxStatOK = 插件
已处理(宿主不再把事件给其他对象),ReplyDefault = 未处理。
- `OfxInteractSuiteV1`(ofxInteract.h:534):interactSwapBuffers /
interactRedraw 把请求记入 `swap_requested`/`redraw_requested`
(app 侧轮询面);interactGetPropertySet 返回 interact 属性集句柄。
- interact 属性集走现有 property suite:PixelScale / ViewportSize /
BackgroundImage / SuggestedColour / SlaveToParam / BackgroundColour /
BitDepth / HasAlpha(ofxInteract.h 的 PropertiesInteract;ViewportSize
取 OFX 1.3 名 "OfxInteractPropViewport"、BackgroundImage 与 Idle 为
任务契约扩展)。
- **`suites/draw.rs`**(`OfxDrawSuiteV1`,OFX 1.5,vendored ofxDrawSuite.h):
getColour / setColour / setLineWidth / setLineStipple / draw / drawText。
状态(colour/lineWidth/stipple)保存在 `DrawContext`(每次 draw action
创建、经存活表注册,draw 返回摘除——draw 外调用 → kOfxStatFailed)。
GL 绘制是**真实渲染**:gl_bridge 的 CGL 上下文是 3.2 core(无固定管
线),draw 用最小着色器 + VAO/VBO 按正交投影画线/矩形/多边形/椭圆,
非不透明色按 "over" 合成。drawText 无字体光栅化器 → 如实返回
kOfxStatErrUnsupported。非 macOS 无 GL 桥 → kOfxStatFailed。
- **GL 上下文模型**:宿主在调 draw 前经 `gl_bridge::acquire` 保持
current 整个 action(与 WG1 渲染路径同模型);`acquire` 支持同线程
可重入(测试"一次 acquire 覆盖 FBO 装配 → draw → 回读")。
- vendored 头新增:`ofxDrawSuite.h`、`ofxKeySyms.h`(kOfxPropKeySym/
kOfxPropKeyString + kOfxKey_*)、`ofxPixels.h`(OfxRGBAColourF)——
官方 openfx(BSD-3-Clause),与既有 vendored 头同源。
- 验证:`tests/interact_test.rs`(生命周期 + 事件参数逐条断言插件侧
marker 记录;Escape key_down 的 ReplyDefault 透传;实例销毁连带
destroy)+ GL draw 端到端(`OAK_GPU_TESTS` 门:插件 glClear 暗背景 +
Draw suite setColour(0.9,0.1,0.2,1) + draw(Rectangle) → 回读断言矩形
颜色与背景)。
app 接线(WG3b)公共 API:`Instance::new_interact` / `Instance::interact` /
`Instance::describe_interact`、`Interact::{describe, create_instance, draw,
pen_motion, pen_down, pen_up, key_down, key_up, idle, gain_focus, lose_focus,
destroy, props, swap_requested, redraw_requested}`、`host::KEY_*` 关键码、
`fetch_suite("OfxInteractSuite"/"OfxDrawSuite", 1)`。
## 与 M11 §3.5 验收的对照(第 1+2 期现状)
| 验收项 | 状态 |
@@ -243,12 +320,15 @@ crates/,不动 src/(app)与 gpui/。
`oakrender::eval::set_plugin_executor`(依赖反转),duplicator
装进 `oaknode::nodes::plugin::set_plugin_duplicator`。
- `set_project_extent(w,h)`:normalised 坐标默认值换算基准。
- **`gl_bridge.rs`(新增,spike 文档)**:`texture_id` 桩的 GL 互
操作评估——方案 A(wgpu-hal GL 互操作)在 macOS 不可行(Metal
后端无 GL 命名空间);方案 B(独立离屏 GL 上下文 + 回读 +
wgpu upload)技术可行但暂缓(无真实 GL 插件可验证 + 需新 GL 依
赖 + 每帧同步回读 stall)。`texture_id` 保持恒 0,GL 插件经 CPU
render action 正确出帧。
- **`gl_bridge.rs`(方案 B 落地)**:`texture_id` 桩的 GL 互操作评估
结论——方案 A(wgpu-hal GL 互操作)在 macOS 不可行(Metal 后端无
GL 命名空间);**方案 B(独立离屏 GL 上下文 + 回读)已落地**:
macOS CGL 离屏上下文(进程级共享、`acquire()` 串行 + current)、
输出 GL 纹理/FBO 挂载、glReadPixels 回读装帧(垂直翻转、格式转
换)、输入 clip 真实 GL 纹理上传。use_opengl 决策与 GL suite 的
OpenGLTextureIndex 接通真实 GL 名。Linux(EGL)/Windows(WGL)
预留 cfg stub。验证:单元测试(clear → 回读断言)+ 端到端
(GL 测试插件真实渲染)经 `OAK_GPU_TESTS` 门。
- **`progress.rs`**:新增 `UiProgressReporter` trait +
`ReporterFactory` + `set_reporter_factory`(app 注入点)。render
未装 C 回调时装静默报告器,progressStart 携 (label,message) 经
@@ -297,9 +377,10 @@ cargo tarpaulin --out stdout --features test-stubs # 覆盖率门槛
(`bridge::*::stub`),ffi/clip/param 的桥调用(含像素读写、
节点回写、undo 打包)全链路可跑——**覆盖率以该模式为准**
(M11 第 1 期实测 82.93% 行覆盖;第 2 期门槛 ≥80%,见 COVERAGE);
- 最小测试插件(cbits/oak_test_plugin.c,三个入口:
org.oak.test-plugin / org.oak.test-plugin.gl /
org.oak.test-plugin.identity)由 build.rs 编译为共享库,
- 最小测试插件(cbits/oak_test_plugin.c,四个入口:
org.oak.test-plugin / org.oak.test-plugin.gl /
org.oak.test-plugin.identity / org.oak.test-plugin.interact)由
build.rs 编译为共享库,
`common::test_plugin_dir` 运行时装配成 bundle;不可用时相关用例
skip;
- 宿主单例无锁:触碰宿主面的用例经 `common::with_host` 串行化。
@@ -336,6 +417,9 @@ TDD:测试声明与实现声明同步冻结(tests/,函数体 `todo!()`)
(偏好采纳、交叉引用解析、输出写回;ACEScg 工作空间)。
- `gl_render_test.rs` — GL suite 往返/错误路径/像素深度协商矩阵 +
GL render 路径端到端(无 GPU 优雅跳过策略见上)。
- `interact_test.rs` — Interact 宿主:生命周期 + 事件调用面(插件侧
marker 逐条断言)、Escape 状态透传、实例销毁连带、draw 真实 GL 渲染
断言(`OAK_GPU_TESTS` 门)。
- `render_driver_test.rs` — render_job CPU 路径(序列括号、多输入、
参数覆盖、isIdentity 透传像素断言、无桩降级)。
- `bridge_test.rs` — node/render/undo 三桥(`--features test-stubs`