Files
oak-editor/docs/zh/plans/completed/r7-pure-abi-plan.md
T
Mike-Solar dd5508e571 docs: archive completed C ABI campaign docs under plans/completed/
Move the finished migration campaign docs (handoffs v3-v6, roadmap,
R5 guides, R6 cleanup, R7 pure-ABI) from docs/zh/ into
docs/zh/plans/completed/ with an archive README; fix all
cross-references; refresh plans/README.md index (active plans now
marked unlocked).
2026-07-27 01:52:52 +08:00

229 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# R7 计划:从"nm=0"到 Rust-ready 纯 C ABI
> 面向执行者(Qwen 3.8 Max),自包含。工作分支:`c-abi-migration`。
> **背景**R6 已使 oak-editor / oak-render-worker 的 `U _ZN5olive` = 0
> `d13e4e800`)。但按 riir.md 的目标(engine 拆模块 → Rust 重写),
> 还差两层皮:
> 1. `oakengine/display.h` 的灰色契约——签名是 `void *`,语义却是 C++
> (往调用方内存写 `std::shared_ptr`、参数实为 `olive::VideoParams*`);
> 2. liboakengine.so 仍导出 3486 个 C++ 符号(`nm -D --defined-only |
> grep " T _Z"`),Rust 模块化拆分要求导出面只剩 `oakengine_*`。
>
> 三条红线照旧(禁 inline 化 engine 实现、禁 no-op stub、禁 dlsym)。
> 每步闭环:全量构建 0 error → 全量 ctest 绿 → 立即提交。
> flaky 规则:`oak_cli_transcode`/`oakengine_export_test`/`olive-gtest`
> 单独重跑一次,连续两次失败才算回归(`oak_cli_transcode` 的间歇
> SEGFAULT 是预存问题,手动跑通过为准)。
---
## R7-Adisplay.h 灰色契约 POD 化
### A.1 现状问题(为什么 nm=0 还不够)
`engine/include/oakengine/display.h` 的 11 个函数签名全是 `void *`
但注释约定:
- `out_texture/out_frame` 指向调用方内存中的 `TexturePtr/FramePtr`
`std::shared_ptr`),engine 在其上拷贝构造 shared_ptr
- `video_params` 实为 `const olive::VideoParams*`
- `color_job` 实为 `const olive::ColorTransformJob*`
Rust 无法安全持有 shared_ptr、无法构造 C++ VideoParams。app 侧 47 处
`TexturePtr/FramePtr` 成员/变量(18 文件,见 A.4)同样要换。
### A.2 新契约(设计钉死,照此实现)
**所有权协议(核心决策,不许改)**:纹理/帧句柄是不透明指针,指向
engine 堆上的控制块(内部持有 `std::shared_ptr`,实现细节对 ABI 不可见)。
所有权经显式 retain/free 转移,禁止任何"写入调用方内存的 shared_ptr"。
```c
/* engine/include/oakengine/display.h —— 重写版 */
/* ---- 渲染器 ---- */
OAKENGINE_API void *oakengine_display_renderer_create_dynamic(
const char *backend_id, void *parent_qobject);
OAKENGINE_API void *oakengine_display_renderer_create_opengl(
void *parent_qobject);
OAKENGINE_API int oakengine_display_renderer_init(void *renderer,
void *gl_context);
OAKENGINE_API void oakengine_display_renderer_destroy(void *renderer);
/* ---- 纹理(句柄 = OakEngineDisplayTexture*,不透明) ---- */
/* params 用 oak_video_params PODoakengine/videoparams.h 已有),
* 不再是 const void*。 */
OAKENGINE_API void *oakengine_display_texture_create(
void *renderer, const oak_video_params *params,
const void *pixels, int linesize);
/* retain:返回同一句柄并把内部引用计数 +1(跨线程移交用,
* 见 A.3 协议)。free:-1,归零时释放。二者对 NULL 均为 no-op。 */
OAKENGINE_API void *oakengine_display_texture_retain(void *texture);
OAKENGINE_API void oakengine_display_texture_free(void *texture);
OAKENGINE_API int oakengine_display_texture_upload(
void *texture, const void *pixels, int linesize);
OAKENGINE_API int oakengine_display_texture_download(
void *texture, void *pixels, int linesize);
/* 只读属性查询(替代 texture->params()/width()/format() 等) */
OAKENGINE_API int oakengine_display_texture_get_params(
const void *texture, oak_video_params *out);
OAKENGINE_API int oakengine_display_texture_id(const void *texture);
/* ---- 帧(句柄 = OakEngineCodecFrame*,不透明,同协议) ---- */
OAKENGINE_API void *oakengine_codec_frame_create(void);
OAKENGINE_API void *oakengine_codec_frame_retain(void *frame);
OAKENGINE_API void oakengine_codec_frame_free(void *frame);
OAKENGINE_API int oakengine_codec_frame_set_video_params(
void *frame, const oak_video_params *params);
OAKENGINE_API int oakengine_codec_frame_get_params(
const void *frame, oak_video_params *out);
OAKENGINE_API int oakengine_codec_frame_allocate(void *frame);
OAKENGINE_API void *oakengine_codec_frame_data(void *frame);
OAKENGINE_API int oakengine_codec_frame_linesize(const void *frame);
/* ---- 色彩管理 blit ---- */
/* oak_color_transform_job POD(新定义,字段以 engine
* ColorTransformJob 拍平:processor 句柄 + input/output 空间 id +
* 各向异性等)。 */
OAKENGINE_API int oakengine_display_renderer_blit_color_managed(
void *renderer, const oak_color_transform_job *job,
void *dst_texture, const oak_video_params *params);
/* 跨后端纹理下载(viewerdisplay 的 download_from_texture 路径) */
OAKENGINE_API int oakengine_display_renderer_download_from_texture(
void *renderer, int texture_id, const oak_video_params *params,
void *dst_pixels, int linesize);
```
engine 实现(`engine/src/capi/display.cpp` 重写):
```cpp
struct OakEngineDisplayTexture { olive::TexturePtr ptr; };
struct OakEngineCodecFrame { olive::FramePtr ptr; };
// create: new OakEngineDisplayTexture{renderer->create_texture(...)}
// retain/free: new/delete 控制块(引用计数即 shared_ptr 自身)
// POD↔C++oak_video_params ↔ olive::VideoParams 的转换函数若
// capi 已有(viewer.cpp 的 get 路径)就抽成内部共享 helper
// (放 engine/src/capi/videoparamsinternal.h),不许复制粘贴第三份。
```
### A.3 跨线程移交协议(最容易写错的地方,钉死)
`viewerdisplay``load_frame_`/`load_texture_` 在解码线程生产、
显示线程消费。原语义靠 shared_ptr 引用计数保活。新协议:
1. 生产侧 `oakengine_display_texture_retain(t)` 后写入共享槽;
2. 消费侧取走句柄,旧句柄 `free`
3. 槽清空时持有一方负责 `free`
**每个 retain 必须配对恰好一个 free**。写完后 grep 审计配对数。
### A.4 app 侧触点清单(47 处,按文件做,每文件一提交)
| 文件 | 处数 | 要点 |
|---|---|---|
| `app/widget/viewer/viewerdisplay.{h,cpp}` | 19+15 | 最大。`texture_`/`load_texture_`/gizmo 纹理全换句柄;析构与各 reset 路径补 free;A.3 协议主要在这里 |
| `app/widget/scope/scopebase/scopebase.{h,cpp}` | 6+9 | 同模式 |
| `app/widget/manageddisplay/manageddisplay.cpp` | 6 | create_texture/blit/download |
| `app/widget/viewer/viewer.{h,cpp}` | 1+9 | FramePtr 成员换句柄 |
| `app/widget/multicam/multicamdisplay.{h,cpp}` | 1+4 | |
| `app/widget/scope/histogram/histogram.{h,cpp}` | 2+1 | |
| `app/widget/scope/waveform/waveform.{h,cpp}` | 1+1 | |
| `app/widget/scope/vectorscope/vectorscope.{h,cpp}` | 1+1 | |
| `app/panel/viewer/viewerbase.h``app/panel/scope/scope.{h,cpp}` | 3 | |
完成判据:app 全仓库 grep `TexturePtr|FramePtr` = 0
`oakengine/display.h` 全文无 `shared_ptr`、无 `olive::` 出现在**签名**
(注释里也不许写 "olive::TexturePtr storage" 这种约定)。
### A.5 验证
- 新增 `engine/tests/oakengine_display_test.cpp`(无 GL 环境测错误
路径与 retain/free 配对;GL 相关断言用现有 backend 检测跳过模式)。
- `olive-gtest``ViewerDisplayReproTest` 三个可跑通用例**必须全过**
(这是显示路径的回归网,挂一个就是真挂)。
- 手动验证(报告里写明):打开素材 → 画面非黑;scope 面板渲染正常。
---
## R7-Bengine visibility 收口(3486 → 只导出 oakengine_*
### B.1 原理(已具备的条件)
`OAKENGINE_API` 在 GCC/Clang 已是
`__attribute__((visibility("default")))`export.h:40)。给
`oakengine` 目标加 `-fvisibility=hidden` 后,只有 `oakengine_*` 导出。
`oakgl`/`oakvulkan` 两个动态后端同理(已有 `*-cabi-check` OBJECT
目标,顺带确认它们导出面也只剩 C ABI)。
### B.2 唯一难点:测试链接
隐藏符号后,直接引用 engine C++ 内部的测试会断链:
- `olive-gtest`**1006** 个 `U _ZN5olive`
- `timeline-tests`29
- `oakengine_export_test`9`make_oakengine_test` 族里引用内部的)
- `compositing-tests`0(纯 facade,无碍)
**解法(钉死)**engine 源码改出 OBJECT 库,测试链对象文件而非
`.so`
```cmake
# engine/CMakeLists.txt
add_library(oakengine-obj OBJECT ${OLIVE_SOURCES})
# POSITION_INDEPENDENT_CODE ON
add_library(oakengine SHARED $<TARGET_OBJECTS:oakengine-obj>)
target_compile_options(oakengine-obj PRIVATE -fvisibility=hidden)
# 引用 engine C++ 内部的测试目标:
target_link_libraries(<test> PRIVATE oakengine-obj) # 替代 oakengine
```
- `olive-gtest`tests/gtest/CMakeLists.txt)改链 `oakengine-obj` +
`oakengine`facade 符号从 .so 来,避免重复定义;若 ODR 冲突则只链
oakengine-obj,先把 facade 函数符号在 object 里的重复问题解决——
二选一,以链接通过且 ctest 全绿为准,把选择写进提交信息)。
- `timeline-tests`、引用内部的 `oakengine_*_test` 同法。
- **禁止**:为了让测试过而把 engine 内部符号加 visibility("default")
白名单——那是开天窗。
### B.3 验证
```
nm -D --defined-only cmake-build-debug/engine/liboakengine.so | grep -c " T _Z" # 目标 0
nm -D --defined-only cmake-build-debug/engine/liboakengine.so | grep -c " T " # 应等于 oakengine_* 函数数
nm -D cmake-build-debug/app/oak-editor | grep -c " U _ZN5olive" # 必须仍为 0
```
全量构建 0 error + 全量 ctest 绿(45 个)才提交。
---
## R7-C(低优先级,时间够再做):app 的 engine C++ 头清理
app 仍 include ~40 个 engine C++ 头(`grep -rn '#include "' app/ |
grep -E '"(node|render|timeline|undo|task|pluginSupport)/'`)。不产生
符号引用(nm=0 已证),但 RIIR 拆模块时这些 include 会全部失效。
逐个换 facade/句柄头(`cliphandle.h``nodevaluehandle.h` 模式)。
**本批不设完成判据**,收尾时把剩余清单写进 riir.md 附录即可。
## 验收(R7 完成判据)
1. `display.h` 全文无 C++ 类型签名/契约注释;app 无 TexturePtr/FramePtr。
2. liboakengine.so ` T _Z` = 0oak-editor ` U _ZN5olive` 保持 0。
3. 全量构建 0 error;全量 ctest 绿。
4. 更新 `../riir.md` 状态(边界已纯 → 可进 Step 1 拆分)、
`facade-migration-roadmap.md` R7 批次记录。
5. 向用户报告,由用户宣布进入 riir.md §4 的模块拆分阶段。
---
## 状态:R7 已完成(eb634b53e2026-07-27
- R7-A ✅ display.h 已按 §A.2 契约重写(POD + 句柄 retain/free),
app TexturePtr/FramePtr 清零。
- R7-B ✅ liboakengine.so 导出 3486 → 19version script19 个为
oakgl/oakvulkan dlopen 插件 ABIver 文件内注释钉死)。
- 复核修复 1 处:viewer.cpp 生产端推裸 `void *`、消费端解
`OakSharedBufferPtr` 的类型不匹配(vulkan 显示路径全挂),
已改为 oak_make_shared_frame/texture(retain)。
- R7-Capp 的 ~40 个 engine C++ 头清理)未做,转入 riir/ 模块
拆分阶段顺带处理(M 系列手册的适配头天然覆盖)。
- 验收:构建 0 error、ctest 45/45、双二进制 nm U _ZN5olive = 0。
**RIIR 模块拆分(../riir/ M1-M9)解锁。**