Olive/Oak 模块化与多进程渲染:显式加载 + 纯 C 接口方案
状态:详细实施计划
范围:仅制定方案,不涉及代码变更。
核心目标:
- 所有业务模块编译为动态库,主进程显式加载(
dlopen/LoadLibrary),通过纯 C 接口交互。- 渲染引擎拆分为独立可执行文件
olive-renderer,采用**"用完即弃"**的进程模型:每帧(或每几帧)启动一个新进程,渲染完成后立即退出,最大限度避免锁和状态同步问题。
目录索引
| 文件 | 内容 |
|---|---|
README.md |
本文档:总体架构、设计哲学、目录索引 |
09-c-api-design.md |
先读此文件:C API 设计规范、显式加载器、内存管理、错误处理、类型映射总纲。所有其他库的 C API 都遵循此规范。 |
01-olivecore.md |
libolivecore.so:基础数据类型库(Rational, Color, TimeRange, PixelFormat, SampleBuffer 等) |
02-olivecodec.md |
libolivecodec.so:编解码库(Decoder, Encoder, Frame, Stream) |
03-oliveplugin.md |
liboliveplugin.so:OFX 插件宿主支持 |
04-oliveaudio.md |
liboliveaudio.so:音频播放与处理 |
05-olivenode.md |
libolivenode.so:节点图系统(Node, NodeGraph, Project, Param, Keyframe, Timeline, Undo) |
06-oliverender.md |
liboliverender.so:渲染引擎抽象(RenderContext, RenderJob, RenderResult) |
07-oliveui.md |
liboliveui.so:UI 层(Widget, Panel, Window, Dialog) |
08-olive-renderer.md |
olive-renderer 可执行文件:多进程渲染的"用完即弃"模型详细设计 |
10-implementation-roadmap.md |
10 周小步快跑实施路线图,含每周任务、验收标准、回退策略 |
11-ipc-protocol.md |
渲染子进程的 IPC 协议(命令行参数、stdin JSON、stdout NDJSON、共享内存) |
总体架构图
┌─────────────────────────────────────────────────────────────────────────────┐
│ olive-editor(主进程,GUI) │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ ModuleLoader(显式加载管理器) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │dlopen │ │dlopen │ │dlopen │ │dlopen │ │dlopen │ │ │
│ │ │olivecore │ │olivecodec│ │olivenode │ │oliverender││oliveui │ │ │
│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │
│ │ │ │ │ │ │ │ │
│ │ └────────────┴────────────┴────────────┘ │ │ │
│ │ 纯 C 接口交互 │ │ │
│ │ ▼ │ │
│ │ ┌──────────────┐ │ │
│ │ │ QProcess │ │ │
│ │ │ 启动/等待/回收 │ │ │
│ │ └──────┬───────┘ │ │
│ └───────────────────────────────────────────────────────────┼───────────┘ │
│ │ │
├──────────────────────────────────────────────────────────────┼───────────────┤
│ │ │
│ ┌───────────────────────────────────────────────────────────┘ │
│ │ olive-renderer(子进程,无 GUI,"用完即弃") │
│ │ │
│ │ 启动参数:--node-graph=/tmp/g.xml --time=1001/30000 --output-shm=/o_123 │
│ │ │
│ │ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ │ 1. 初始化 Qt Core │ │
│ │ │ 2. 初始化 OpenGL 离屏上下文 │ │
│ │ │ 3. 加载节点图 XML │ │
│ │ │ 4. 执行渲染(RenderProcessor + OpenGLRenderer) │ │
│ │ │ 5. 将帧数据写入共享内存 │ │
│ │ │ 6. 输出 JSON 结果到 stdout │ │
│ │ │ 7. 清理并退出(return 0) │ │
│ │ └─────────────────────────────────────────────────────────────────────┘ │
│ └────────────────────────────────────────────────────────────────────────────┘
│
│ 所有动态库共享的基础依赖:QtCore, FFmpeg, OpenColorIO 等(由操作系统加载器解析)
└─────────────────────────────────────────────────────────────────────────────┘
核心设计哲学
1. 显式加载 + 纯 C 接口
- 主进程不链接任何业务动态库。
olive-editor的main.cpp和core.cpp中没有任何对业务模块的#include(除 C API 头文件外)。 - 所有跨模块边界的交互通过纯 C 函数完成,使用**不透明指针(Opaque Pointer)**封装 C++ 对象。
- 动态库内部可以继续使用 C++、Qt、STL、虚函数、模板等任意特性,但对外仅暴露 C 接口。
为什么用 C 接口而非 C++ 类?
- C++ 的 ABI(虚表布局、name mangling、异常传播)在不同编译器/版本间不兼容。
- C 接口的符号名干净(无 mangling),
dlsym可直接查找。 - 未来如果需要,C 接口可被 Python、Rust、C# 等语言直接绑定。
2. "用完即弃"的渲染进程
- 每一帧(或每 N 帧)渲染任务 = 一个独立的操作系统进程。
- 进程启动时接收完整的渲染参数和节点图,渲染完成后立即
exit(0)。 - 不存在常驻渲染进程,因此不需要:心跳检测、崩溃恢复、状态同步、读写锁、graph_ref 缓存、复杂的取消机制。
- 主进程如果需要取消渲染,直接
QProcess::kill()即可。
可能的问题与回退:
- 若进程启动开销(
QProcess::start()+ OpenGL 上下文初始化)导致实时预览帧率不足,可回退为**"批处理模式"**:每 3–5 帧共享一个进程,或预先启动一个进程池(但进程池内的进程仍不共享状态,每个进程只处理一个批次后自杀)。详见08-olive-renderer.md。
3. 小步快跑
- 每个模块的改造都是独立、可回退、可并行的。
- 不改现有 C++ 类定义,只在其上层新增 C 封装层(
api/<module>_api.cpp)。 - 保留原有 OBJECT 库编译方式,通过 CMake 选项
-DOLIVE_DYNAMIC_MODULES=ON切换。 - 每完成一个模块,就通过该模块的单元测试验证,再进入下一个模块。
快速开始(阅读顺序)
- 先读
09-c-api-design.md:理解 C API 的约定、显式加载器、内存管理规则。 - 再读
08-olive-renderer.md:理解最核心的架构变革——多进程渲染。 - 然后按任意顺序阅读
01-到07-:各业务模块的具体 C API 设计和实施步骤。 - 最后读
10-implementation-roadmap.md:10 周实施计划,了解如何排期和验收。 - 参考
11-ipc-protocol.md:渲染子进程的通信协议细节。